PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.5.0
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.5.0
8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 8.5.5 8.5.4 8.5.3 8.5.2 All 533 releases
chatbot / includes / admin / templates / rag.php

rag.php in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.5.0, at includes/admin/templates/rag.php

833 lines 45.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) exit; // Exit if accessed directly
3 /**
4 * AI Assistant Template - RAG Feature Extended
5 * @package Botmaster
6 */
7 if (get_option('wpchatbot_license_valid') == 'master' && get_option('wpchatbot_license_valid') == 'professional') {
8 update_option('wpchatbot_license_valid', 'starter');
9 }
10
11 $wpchatbot_license_valid = get_option('wpchatbot_license_valid');
12 // $wpchatbot_license_valid = 'starter';
13
14 ?>
15
16 <div class="qcl-openai">
17
18
19
20 <h2 class="nav-tab-wrapper">
21 <a href="#qcld-rag-settings-tab" class="nav-tab nav-tab-active"> Setting options</a>
22 <a href="#rag-sync" class="nav-tab">Sync and upload options</a>
23 <a href="#rag-sources" class="nav-tab">Manage Sources</a>
24 <a href="#rag-database" class="nav-tab">KnowledgeBase Database</a>
25 </h2>
26
27 <div id="qcld-rag-settings-tab" class="qcld-tab-content active">
28 <!-- ===========================
29 EMBEDDING SOURCE OPTIONS
30 ============================ -->
31 <div class="wrap">
32 <h3>Choose Data Sources to Embed</h3>
33
34 <div class="mb-3">
35 <input type="checkbox" id="rag_embed_pages" <?php checked(get_option('rag_embed_pages'), '1'); ?>>
36 <label for="rag_embed_pages">Pages</label>
37 </div>
38
39 <div class="mb-3">
40 <input type="checkbox" id="rag_embed_posts" <?php checked(get_option('rag_embed_posts'), '1'); ?>>
41 <label for="rag_embed_posts">Posts</label>
42 </div>
43 <div class="mb-3">
44 <input type="checkbox" id="rag_embed_str" <?php checked(get_option('rag_embed_str'), '1'); ?>>
45 <label for="rag_embed_str">Simple Text Responses</label>
46 </div>
47 <div class="mb-3">
48 <?php
49 $custom_post_types = get_post_types(['public' => true, '_builtin' => false], 'objects');
50 $selected_cpts = get_option('rag_embed_cpts', []);
51 ?>
52
53 <label><strong>Custom Post Types:</strong></label><br>
54
55 <div class="rag_embed_cpts_wrapper">
56 <?php foreach ($custom_post_types as $cpt): ?>
57 <div class="rag_cpt_checkbox">
58 <input type="checkbox" class="rag_embed_cpts_checkbox"
59 id="rag_cpt_<?php echo esc_attr($cpt->name); ?>"
60 value="<?php echo esc_attr($cpt->name); ?>"
61 <?php echo in_array($cpt->name, $selected_cpts) ? 'checked' : ''; ?>>
62 <label for="rag_cpt_<?php echo esc_attr($cpt->name); ?>"><?php echo esc_html($cpt->label); ?></label>
63 </div>
64 <?php endforeach; ?>
65 </div>
66 <p class="description">Select multiple custom post types to embed.</p>
67 </div>
68
69
70 </div>
71 <!-- ===========================
72 EXECUTION BUTTON
73 ============================ -->
74 <div class="wrap my-4">
75 <p style="color: red"> <b><?php esc_html_e('Please connect to an AI service like OpenAI or Gemini before embedding. ', 'chatbot'); ?></b><b><a target="_blank" href="https://wpbot.pro/docs/knowledgebase/how-to-use-an-embedded-vector-database-and-rag-to-get-customized-responses-from-ai/"><?php esc_html_e('Check this Tutorial for more details.', 'chatbot'); ?></a></b></p>
76 <form method="post" id="rag_embed_form">
77 <input type="hidden" name="embed_all_sources" value="1">
78 <button type="button" id="rag_embed_btn" class="button button-primary">Embed All Selected Sources</button>
79 </form>
80
81 <?php
82 if (isset($_POST['embed_all_sources'])):
83 if( ( get_option( 'ai_enabled') == 1 && get_option('open_ai_api_key') ) || ( get_option('qcld_gemini_enabled') == 1 && get_option('qcld_gemini_api_key') ) || ( get_option('qcld_openrouter_enabled') == 1 && get_option('qcld_openrouter_api_key') ) ){
84 ?>
85 <h3>Embedding started...</h3>
86 <?php Qcld_Bot_Rag::instance()->wp_rag_embed_all_sources(); ?>
87 <?php }else{ ?>
88 <Script>
89 swal.fire('', 'Please connect to an AI service like OpenAI or Gemini with API key before embedding.', 'warning');
90 </Script>
91 <?php
92 }
93 endif;
94 ?>
95 </div>
96 <!-- ===========================
97 SAVE SETTINGS BUTTON
98 ============================ -->
99 <div class="wrap">
100 <button class="qcld-btn-primary" id="save_rag_setting">Save Settings</button>
101 </div>
102 </div>
103
104
105 <div id="rag-sync" class="qcld-tab-content">
106 <?php if ( $wpchatbot_license_valid != 'master' && $wpchatbot_license_valid != 'professional'): ?>
107 <div class="wrap">
108 <div style="background-color: #fee; border: 1px solid #c33; padding: 15px; margin: 20px 0; border-radius: 4px;">
109 <p style="color: #c33; font-weight: bold; margin: 0; font-size: 14px;">
110 These options are available with the WPBot Pro <a href="https://www.wpbot.pro/pricing/" target="_blank" style="color: #c33; text-decoration: underline;">Professional</a> and <a href="https://www.wpbot.pro/pricing/" target="_blank" style="color: #c33; text-decoration: underline;">Master</a> Licenses
111 </p>
112 </div>
113 </div>
114 <?php endif; ?>
115
116 <div style="<?php if ( $wpchatbot_license_valid != 'master' && $wpchatbot_license_valid != 'professional'){ echo 'opacity:0.5; pointer-events:none;'; } ?>">
117 <!-- ===========================
118 SYNC SETTINGS
119 ============================ -->
120 <!-- ===========================
121 PDF UPLOAD (AJAX)
122 ============================ -->
123 <div class="wrap">
124 <h3>Sync Settings</h3>
125
126 <div class="mb-3">
127 <input type="checkbox" id="rag_auto_sync_enabled" <?php checked(get_option('rag_auto_sync_enabled'), '1'); ?>>
128 <label for="rag_auto_sync_enabled"><strong>Enable Auto Sync (on Save)</strong></label>
129 <p class="description">Automatically update embeddings when a post or product is saved/updated.</p>
130 </div>
131 <hr>
132 <?php
133 $sync_interval = get_option('rag_sync_interval', 'daily');
134 $interval_labels = [
135 'hourly' => '(Hourly)',
136 'twicedaily' => '(Twice Daily)',
137 'daily' => '(Daily)',
138 'weekly' => '(Weekly)',
139 ];
140 $interval_desc = [
141 'hourly' => 'once per hour',
142 'twicedaily' => 'twice per day',
143 'daily' => 'once per day',
144 'weekly' => 'once per week',
145 ];
146 $current_label = $interval_labels[$sync_interval] ?? '(Daily)';
147 $current_desc = $interval_desc[$sync_interval] ?? 'once per day';
148 ?>
149
150 <div class="mb-3">
151 <input type="checkbox" id="rag_googlesheets_autosync_enabled" <?php checked(get_option('rag_googlesheets_autosync_enabled'), '1'); ?>>
152 <label for="rag_googlesheets_autosync_enabled"><strong>Enable Google Sheets Auto Sync <span class="rag-sync-label"><?php echo esc_html( $current_label ); ?></span></strong></label>
153 <p class="description">Automatically re-sync all Google Sheets in the knowledge base <span class="rag-sync-desc"><?php echo esc_html( $current_desc ); ?></span>.</p>
154 </div>
155
156 <div class="mb-3">
157 <input type="checkbox" id="rag_googledocs_autosync_enabled" <?php checked(get_option('rag_googledocs_autosync_enabled'), '1'); ?>>
158 <label for="rag_googledocs_autosync_enabled"><strong>Enable Google Docs Auto Sync <span class="rag-sync-label"><?php echo esc_html( $current_label ); ?></span></strong></label>
159 <p class="description">Automatically re-sync all Google Docs in the knowledge base <span class="rag-sync-desc"><?php echo esc_html( $current_desc ); ?></span>.</p>
160 </div>
161
162 <div class="mb-3">
163 <label for="rag_sync_interval"><strong>Sync Interval:</strong></label>
164 <select id="rag_sync_interval">
165 <option value="daily" <?php selected(get_option('rag_sync_interval', 'daily'), 'daily'); ?>>Once Daily</option>
166 <option value="twicedaily" <?php selected(get_option('rag_sync_interval'), 'twicedaily'); ?>>Twice Daily</option>
167 <option value="hourly" <?php selected(get_option('rag_sync_interval'), 'hourly'); ?>>Hourly</option>
168 <option value="weekly" <?php selected(get_option('rag_sync_interval'), 'weekly'); ?>>Weekly</option>
169 </select>
170 <p class="description">Choose how often to run the automated synchronization.</p>
171 </div>
172
173 </div>
174 <div class="wrap">
175 <h3>Upload PDF for RAG</h3>
176
177 <form id="rag-pdf-form">
178 <input type="file" id="rag-pdf-files" name="rag_pdf[]" multiple accept="application/pdf" />
179 <br><br>
180 <button type="submit" class="button button-primary" id="rag-pdf-submit">Upload & Embed PDF</button>
181 <button type="button" class="button" id="rag-media-pdf-btn">Select PDF from Media Library</button>
182 <button type="button" class="button" id="qcld-rag-list-pdf-btn">List PDF Files from Media Library</button>
183 <span id="rag-pdf-status" style="margin-left: 10px;"></span>
184 </form>
185
186 <div id="qcld-pdf-list-container" style="display:none; margin-top:20px; border:1px solid #ddd; padding:15px; background:#fff;">
187 <h4>Available PDF Files in Media Library</h4>
188 <div id="qcld-pdf-items" style="max-height:300px; overflow-y:auto; margin-bottom:15px;">
189 <p>Loading files...</p>
190 </div>
191 <button type="button" class="button button-primary qcld-rag-index-selected-media" data-type="pdf">Index Selected PDF Files</button>
192 <button type="button" class="button qcld-rag-close-media-list">Close List</button>
193 </div>
194
195 <div id="rag-pdf-output" style="margin-top: 15px;"></div>
196 </div>
197
198 <!-- ===========================
199 CSV UPLOAD (AJAX)
200 ============================ -->
201 <div class="wrap">
202 <h3>Upload CSV Data for RAG</h3>
203 <p>Upload CSV files with data to be embedded. Each row will be processed as a separate document. <a href="<?php echo esc_url( plugin_dir_url(__FILE__).'download/rag_test_data.csv' ); ?>">Download Test Data</a></p>
204
205 <form id="rag-csv-form">
206 <input type="file" id="rag-csv-files" name="rag_csv[]" multiple accept=".csv,text/csv" />
207 <br><br>
208 <button type="submit" class="button button-primary" id="rag-csv-submit">Upload & Embed CSV</button>
209 <span id="rag-csv-status" style="margin-left: 10px;"></span>
210 </form>
211
212 <div id="rag-csv-output" style="margin-top: 15px;"></div>
213 </div>
214
215 <!-- ===========================
216 DOCX UPLOAD (AJAX)
217 ============================ -->
218 <div class="wrap">
219 <h3>Upload Word (.docx) for RAG</h3>
220 <p>Upload MS Word files to be indexed in the knowledge base.</p>
221
222 <form id="rag-docx-form">
223 <input type="file" id="rag-docx-files" name="rag_docx[]" multiple accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
224 <br><br>
225 <button type="submit" class="button button-primary" id="rag-docx-submit">Upload & Embed Word</button>
226 <button type="button" class="button" id="rag-media-docx-btn">Select Word from Media Library</button>
227 <button type="button" class="button" id="qcld-rag-list-docx-btn">List Word Files from Media Library</button>
228 <span id="rag-docx-status" style="margin-left: 10px;"></span>
229 </form>
230
231 <div id="qcld-docx-list-container" style="display:none; margin-top:20px; border:1px solid #ddd; padding:15px; background:#fff;">
232 <h4>Available Word Files in Media Library</h4>
233 <div id="qcld-docx-items" style="max-height:300px; overflow-y:auto; margin-bottom:15px;">
234 <p>Loading files...</p>
235 </div>
236 <button type="button" class="button button-primary qcld-rag-index-selected-media" data-type="docx">Index Selected Word Files</button>
237 <button type="button" class="button qcld-rag-close-media-list">Close List</button>
238 </div>
239
240 <div id="rag-docx-output" style="margin-top: 15px;"></div>
241 </div>
242
243 <!-- ===========================
244 XAML UPLOAD (AJAX)
245 ============================ -->
246 <div class="wrap">
247 <h3>Upload XML Data for RAG</h3>
248 <p>Upload XML files with data to be embedded.</p>
249
250 <form id="rag-xaml-form">
251 <input type="file" id="rag-xaml-files" name="rag_xaml[]" multiple accept=".xaml,text/xml,application/xml" />
252 <br><br>
253 <button type="submit" class="button button-primary" id="rag-xaml-submit">Upload & Embed XAML</button>
254 <span id="rag-xaml-status" style="margin-left: 10px;"></span>
255 </form>
256
257 <div id="rag-xaml-output" style="margin-top: 15px;"></div>
258 </div>
259
260 <!-- ===========================
261 SITEMAP SUBMISSION
262 ============================ -->
263 <div class="wrap">
264 <h3>Submit Sitemap for RAG</h3>
265 <p>Enter your XML Sitemap URL to crawl and embed all pages.</p>
266 <input type="url" id="botmaster_sitemap_url" class="regular-text" placeholder="https://example.com/sitemap.xml" style="width: 100%; max-width: 400px;">
267 <button type="button" id="botmaster_submit_sitemap_btn" class="button button-primary">Process Sitemap</button>
268 <div id="botmaster_sitemap_status" style="margin-top: 10px;"></div>
269 </div>
270
271 <!-- ===========================
272 GOOGLE SHEETS SUBMISSION
273 ============================ -->
274 <div class="wrap">
275 <h3>Submit Google Sheet for RAG</h3>
276 <p>Enter your publicly published Google Sheet URL (Publish to web -> Web page).</p>
277 <input type="url" id="botmaster_googlesheet_url" class="regular-text" placeholder="https://docs.google.com/spreadsheets/d/e/.../pubhtml" style="width: 100%; max-width: 400px;">
278 <button type="button" id="botmaster_submit_googlesheet_btn" class="button button-primary">Process Google Sheet</button>
279
280 <div id="gs_progress_container" style="display:none; margin-top: 20px;">
281 <div class="progress" style="height: 20px; background-color: #f1f1f1; border-radius: 5px; overflow: hidden; border: 1px solid #ddd;">
282 <div id="gs_progress_bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%; height: 100%; background-color: #28a745; transition: width 0.3s ease;"></div>
283 </div>
284 <p id="gs_progress_status" style="margin-top: 10px; font-weight: bold; font-size: 13px;"></p>
285 </div>
286
287 <div id="botmaster_googlesheet_status" style="margin-top: 10px;"></div>
288 </div>
289
290 <!-- ===========================
291 GOOGLE DOCS SUBMISSION
292 ============================ -->
293 <div class="wrap">
294 <h3>Submit Google Doc for RAG</h3>
295 <p>Enter your publicly published Google Doc URL (File -> Share -> Publish to web).</p>
296 <input type="url" id="botmaster_googledoc_url" class="regular-text" placeholder="https://docs.google.com/document/d/.../pub" style="width: 100%; max-width: 400px;">
297 <button type="button" id="botmaster_submit_googledoc_btn" class="button button-primary">Process Google Doc</button>
298
299 <div id="gd_progress_container" style="display:none; margin-top: 20px;">
300 <div class="progress" style="height: 20px; background-color: #f1f1f1; border-radius: 5px; overflow: hidden; border: 1px solid #ddd;">
301 <div id="gd_progress_bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%; height: 100%; background-color: #007bff; transition: width 0.3s ease;"></div>
302 </div>
303 <p id="gd_progress_status" style="margin-top: 10px; font-weight: bold; font-size: 13px;"></p>
304 </div>
305
306 <div id="botmaster_googledoc_status" style="margin-top: 10px;"></div>
307 </div>
308 </div>
309 <h5 style="color: #c33;">Notes: These are based on AI system, so make sure you have enabled any AI features and set up valid API keys. <b><a href="<?php echo esc_url('https://wpbot.pro/docs/knowledgebase/how-to-use-an-embedded-vector-database-and-rag-to-get-customized-responses-from-ai/'); ?>" target="_blank">Check this Tutorial for more details.</a></b></h5>
310 <div class="wrap">
311 <button class="qcld-btn-primary" id="save_rag_setting">Save Settings</button>
312 </div>
313 </div>
314
315 <style>
316 .qcld-tab-content {
317 display: none !important;
318 }
319
320 .qcld-tab-content.active {
321 display: block !important;
322 }
323 </style>
324
325 <script>
326 jQuery(document).ready(function($) {
327 $('#botmaster_submit_sitemap_btn').on('click', function() {
328 var sitemapUrl = $('#botmaster_sitemap_url').val();
329 var statusDiv = $('#botmaster_sitemap_status');
330
331 if (!sitemapUrl) {
332 alert('Please enter a Sitemap URL');
333 return;
334 }
335
336 statusDiv.html('Processing... please wait.');
337 $(this).prop('disabled', true);
338
339 $.post(ajaxurl, {
340 action: 'botmaster_submit_sitemap',
341 sitemap_url: sitemapUrl,
342 nonce: '<?php echo esc_attr( wp_create_nonce('botmaster_kb_nonce') ); ?>'
343 }, function(response) {
344 $('#botmaster_submit_sitemap_btn').prop('disabled', false);
345 if (response.success) {
346 statusDiv.html('<div class="notice notice-success inline"><p>' + response.data.message + '</p></div>');
347 } else {
348 statusDiv.html('<div class="notice notice-error inline"><p>Error: ' + response.data + '</p></div>');
349 }
350 });
351 });
352 });
353 </script>
354 <div id="rag-sources" class="qcld-tab-content">
355 <?php if ( $wpchatbot_license_valid != 'master' && $wpchatbot_license_valid != 'professional'): ?>
356 <div class="wrap">
357 <div style="background-color: #fee; border: 1px solid #c33; padding: 15px; margin: 20px 0; border-radius: 4px;">
358 <p style="color: #c33; font-weight: bold; margin: 0; font-size: 14px;">
359 These options are available with the WPBot Pro <a href="<?php echo esc_url('https://www.wpbot.pro/pricing/'); ?>" target="_blank" style="color: #c33; text-decoration: underline;">Professional</a> and <a href="<?php echo esc_url('https://www.wpbot.pro/pricing/'); ?>" target="_blank" style="color: #c33; text-decoration: underline;">Master</a> Licenses
360 </p>
361 </div>
362 </div>
363 <?php endif; ?>
364
365 <div style="<?php if ( $wpchatbot_license_valid != 'master' && $wpchatbot_license_valid != 'professional'){ echo 'opacity:0.5; pointer-events:none;'; } ?>">
366 <div class="wrap">
367 <h3>Manage Knowledge Base Sources</h3>
368 <p>Manage entire files or spreadsheets instead of individual rows.</p>
369
370 <table class="wp-list-table widefat fixed striped">
371 <thead>
372 <tr>
373 <th class="check-column"><input type="checkbox" id="rag-sources-select-all"></th>
374 <th>Source Name / URL</th>
375 <th>Type</th>
376 <th>Documents</th>
377 <th>Actions</th>
378 </tr>
379 </thead>
380 <tbody id="rag-sources-list">
381 <?php
382 global $wpdb;
383 $table = $wpdb->prefix . 'rag_documents';
384 $sources = $wpdb->get_results("SELECT source_url, source_type, COUNT(*) as count, MAX(title) as display_name FROM {$table} GROUP BY source_url, source_type ORDER BY count DESC"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
385
386 if ($sources) {
387 foreach ($sources as $source) {
388 $display_url = $source->source_url ?: "Manual Upload";
389 ?>
390 <tr data-source-url="<?php echo esc_attr($source->source_url); ?>">
391 <th scope="row" class="check-column"><input type="checkbox" class="rag-source-checkbox" value="<?php echo esc_attr($source->source_url); ?>"></th>
392 <td>
393 <strong><?php echo esc_html($source->display_name); ?></strong><br>
394 <small style="color:#888;"><?php echo esc_html($display_url); ?></small>
395 </td>
396 <td><span class="badge" style="background:#eee; padding:2px 5px; border-radius:3px;"><?php echo esc_html($source->source_type); ?></span></td>
397 <td><?php echo esc_html($source->count); ?> chunks</td>
398 <td>
399 <button class="button rag-source-sync" data-url="<?php echo esc_attr($source->source_url); ?>" data-type="<?php echo esc_attr($source->source_type); ?>">Sync Now</button>
400 <button class="button button-link-delete rag-source-delete" data-url="<?php echo esc_attr($source->source_url); ?>">Delete All</button>
401 </td>
402 </tr>
403 <?php
404 }
405 } else {
406 echo '<tr><td colspan="5">No sources found.</td></tr>';
407 }
408 ?>
409 </tbody>
410 </table>
411
412 <div class="tablenav bottom">
413 <div class="alignleft actions bulkactions">
414 <select id="rag-sources-bulk-action">
415 <option value="-1">Bulk Actions</option>
416 <option value="delete">Delete Selected Sources</option>
417 </select>
418 <button type="button" id="rag-sources-apply-bulk" class="button action">Apply</button>
419 </div>
420 </div>
421 </div>
422
423 <div class="wrap" style="margin-top: 40px; border-top: 1px solid #ddd; padding-top: 20px;">
424 <h3>Database Utilities</h3>
425 <div class="notice notice-info inline">
426 <p>Upgrade your database to the new **High-Speed Binary Format**. This makes similarity searches significantly faster (10x+ speed improvement).</p>
427 </div>
428 <button type="button" id="rag-migrate-btn" class="button button-primary">Run High-Speed Migration</button>
429 <span id="rag-migrate-status" style="margin-left: 10px;"></span>
430 </div>
431 </div>
432 </div>
433
434
435
436
437 <div id="rag-database" class="qcld-tab-content">
438 <!-- ===========================
439 KNOWLEDGE BASE MANAGEMENT
440 ============================ -->
441 <div class="wrap">
442 <h3>Knowledge Base</h3>
443
444 <div class="tablenav top">
445 <div class="alignleft actions bulkactions">
446 <select id="rag-bulk-action-selector">
447 <option value="-1">Bulk Actions</option>
448 <option value="delete">Delete</option>
449 </select>
450 <button type="button" id="rag-apply-bulk-action" class="button action">Apply</button>
451 </div>
452 <div class="alignleft actions">
453 <button type="button" id="rag-delete-all" class="button button-link-delete" style="margin-left: 10px;">Delete All</button>
454
455
456 <div class="alignright actions" style="margin-left: 10px;">
457 <form method="get" style="display:inline-block;" action="?page=wpbot_openAi#ai-knowledge-base-tab#rag-database">
458 <input type="hidden" name="page" value="<?php echo esc_attr( sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification ?>">
459 <?php if (isset($_GET['post_type'])): // phpcs:ignore WordPress.Security.NonceVerification ?>
460 <input type="hidden" name="post_type" value="<?php echo esc_attr( sanitize_text_field( wp_unslash( $_GET['post_type'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification ?>">
461 <?php endif; ?>
462 <p class="search-box" style="margin:0;">
463 <input type="search" id="rag-search-input" name="s" value="<?php echo esc_attr( sanitize_text_field( wp_unslash( $_GET['s'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification ?>" placeholder="Search documents...">
464 <input type="submit" id="search-submit" class="button" value="Search">
465 </p>
466 </form>
467 </div>
468 </div>
469
470 <br class="clear">
471 </div>
472 <?php
473 global $wpdb;
474 $table_rag_documents = $wpdb->prefix . 'rag_documents';
475
476 // Handle Table Creation
477 if (isset($_POST['rag_create_table']) && check_admin_referer('rag_create_table_nonce')) {
478 $charset = $wpdb->get_charset_collate();
479 $sql_rag_documents = "CREATE TABLE $table_rag_documents (
480 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
481 doc_id VARCHAR(100) DEFAULT NULL,
482 title VARCHAR(255) NOT NULL,
483 content LONGTEXT NOT NULL,
484 embedding LONGTEXT NOT NULL,
485 source_type VARCHAR(20) DEFAULT 'post',
486 source_url VARCHAR(255) DEFAULT NULL,
487 file_url TEXT DEFAULT NULL,
488 metadata LONGTEXT DEFAULT NULL,
489 status VARCHAR(50) DEFAULT 'complete',
490 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
491 ) $charset;";
492 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
493 dbDelta($sql_rag_documents);
494 echo '<div class="notice notice-success inline"><p>Database table created successfully.</p></div>';
495 }
496
497 // Check if table exists
498 $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $wpdb->esc_like($table_rag_documents))) === $table_rag_documents; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
499
500 if (!$table_exists) {
501 ?>
502 <div class="wrap">
503 <h3>Knowledge Base Database</h3>
504 <div class="notice notice-warning inline">
505 <p>The <strong><?php echo esc_html($table_rag_documents); ?></strong> table does not exist. Please create it to start using the Knowledge Base.</p>
506 </div>
507 <form method="post">
508 <?php wp_nonce_field('rag_create_table_nonce'); ?>
509 <input type="hidden" name="rag_create_table" value="1">
510 <button type="submit" class="button button-primary">Create Database Table</button>
511 </form>
512 </div>
513 <?php
514 } else {
515 ?>
516 <?php
517 $table_name = $table_rag_documents;
518
519 $search_query = isset($_GET['s']) ? sanitize_text_field(wp_unslash($_GET['s'])) : '';
520
521 if ($search_query) {
522 $like = '%' . $wpdb->esc_like($search_query) . '%';
523 $total_items = $wpdb->get_var($wpdb->prepare("SELECT COUNT(id) FROM {$table_name} WHERE title LIKE %s OR content LIKE %s", $like, $like)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
524 } else {
525 $total_items = $wpdb->get_var("SELECT COUNT(id) FROM {$table_name}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
526 }
527 $items_per_page = 50;
528 $page = isset($_GET['paged']) ? absint(wp_unslash($_GET['paged'])) : 1;
529 $offset = ($page - 1) * $items_per_page;
530 $total_pages = ceil($total_items / $items_per_page);
531
532 $pagination_args = array(
533 'base' => add_query_arg(array('paged' => '%#%', 's' => $search_query), '?page=wpbot_openAi') . '#ai-knowledge-base-tab#rag-database',
534 'format' => '',
535 'prev_text' => __( '&laquo;', 'chatbot' ),
536 'next_text' => __( '&raquo;', 'chatbot' ),
537 'total' => $total_pages,
538 'current' => $page,
539 'type' => 'plain',
540 );
541
542 if ($total_pages > 1) {
543 // translators: %s: number of items.
544 echo '<div class="tablenav-pages"><span class="displaying-num">' . sprintf( esc_html( _n( '%s item', '%s items', $total_items, 'chatbot' ) ), esc_html( number_format_i18n($total_items) ) ) . '</span>';
545 echo wp_kses_post( paginate_links($pagination_args) );
546 echo '</div>';
547 }
548 ?>
549
550 <table class="wp-list-table widefat fixed striped">
551 <thead>
552 <tr>
553 <td id="cb" class="manage-column column-cb check-column">
554 <input type="checkbox" id="rag-select-all">
555 </td>
556 <th>Title</th>
557 <th>Content</th>
558 <th>Source Type</th>
559 <th>URL/File</th>
560 <th>Status</th>
561 <th>Actions</th>
562 </tr>
563 </thead>
564 <tbody id="rag-knowledge-base-list">
565 <?php
566 if ($search_query) {
567 $like = '%' . $wpdb->esc_like($search_query) . '%';
568 $documents = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$table_name} WHERE title LIKE %s OR content LIKE %s ORDER BY created_at DESC LIMIT %d OFFSET %d", $like, $like, $items_per_page, $offset)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
569 } else {
570 $documents = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$table_name} ORDER BY created_at DESC LIMIT %d OFFSET %d", $items_per_page, $offset)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
571 }
572
573 if ($documents) {
574 foreach ($documents as $doc) {
575 ?>
576 <tr id="rag-doc-<?php echo esc_attr( $doc->id ); ?>">
577 <th scope="qcld-row" class="check-column">
578 <input type="checkbox" class="rag-doc-checkbox" value="<?php echo esc_attr( $doc->id ); ?>">
579 </th>
580 <td><?php echo esc_html($doc->title); ?></td>
581 <td>
582 <div style="max-height: 4.5em; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; line-height: 1.5em; font-size: 13px; color: #666;">
583 <?php echo wp_kses_post($doc->content); ?>
584 </div>
585 </td>
586 <td><?php echo esc_html($doc->source_type); ?></td>
587 <td><?php echo esc_html($doc->source_url ?: $doc->file_url); ?></td>
588 <td><?php echo esc_html($doc->status); ?></td>
589 <td>
590 <?php if (!in_array($doc->source_type, ['csv', 'xml', 'xaml','sitemap'])): ?>
591 <button class="button button-small rag-sync-doc" data-id="<?php echo esc_attr( $doc->id ); ?>" title="Re-sync data from source">Sync</button>
592 <?php endif; ?>
593 <button class="button button-small rag-edit-doc" data-id="<?php echo esc_attr( $doc->id ); ?>">Edit</button>
594 <button class="button button-small button-link-delete rag-delete-doc" data-id="<?php echo esc_attr( $doc->id ); ?>">Delete</button>
595 </td>
596 </tr>
597 <?php
598 }
599 } else {
600 echo '<tr><td colspan="7">No documents found in knowledge base.</td></tr>';
601 }
602 ?>
603 </tbody>
604 </table>
605
606 <div class="tablenav bottom">
607 <?php
608 if ($total_pages > 1) {
609 // translators: %s: number of items.
610 echo '<div class="tablenav-pages"><span class="displaying-num">' . sprintf( esc_html( _n( '%s item', '%s items', $total_items, 'chatbot' ) ), esc_html( number_format_i18n($total_items) ) ) . '</span>';
611 echo wp_kses_post( paginate_links($pagination_args) );
612 echo '</div>';
613 }
614 ?>
615 <br class="clear">
616 </div>
617 <?php } ?>
618 </div>
619 </div>
620
621 <!-- Edit Document Modal -->
622 <div id="rag-edit-modal" style="display:none; position:fixed; z-index:99999; left:0; top:0; width:100%; height:100%; background:rgba(0,0,0,0.5);">
623 <div style="background:#fff; margin:10% auto; padding:20px; width:60%; border-radius:5px;">
624 <h4>Edit Knowledge Base Document</h4>
625 <input type="hidden" id="edit-doc-id">
626 <div class="mb-3">
627 <label>Title</label><br>
628 <input type="text" id="edit-doc-title" class="regular-text" style="width:100%;">
629 </div>
630 <div class="mb-3">
631 <label>Content</label><br>
632 <textarea id="edit-doc-content" rows="10" style="width:100%;"></textarea>
633 </div>
634 <p class="description">Note: Updating content will re-generate embeddings.</p>
635 <div class="mt-3">
636 <button id="save-edit-doc" class="button button-primary">Save Changes</button>
637 <button id="close-edit-modal" class="button">Cancel</button>
638 </div>
639 </div>
640 </div>
641 </div>
642
643
644
645 <script>
646 jQuery(document).ready(function($) {
647 // PDF Upload Handler
648 $('#rag-pdf-form').on('submit', function(e) {
649 e.preventDefault();
650
651 var fileInput = $('#rag-pdf-files')[0];
652 if (!fileInput.files.length) {
653 alert('Please select PDF files to upload');
654 return;
655 }
656
657 var formData = new FormData();
658 for (var i = 0; i < fileInput.files.length; i++) {
659 formData.append('rag_pdf[]', fileInput.files[i]);
660 }
661 formData.append('action', 'rag_upload_pdf');
662 formData.append('nonce', '<?php echo esc_js( wp_create_nonce('rag_upload_nonce') ); ?>');
663
664 $('#rag-pdf-submit').prop('disabled', true);
665 $('#rag-pdf-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
666 $('#rag-pdf-output').html('');
667
668 $.ajax({
669 url: ajaxurl,
670 type: 'POST',
671 data: formData,
672 processData: false,
673 contentType: false,
674 success: function(response) {
675 $('#rag-pdf-submit').prop('disabled', false);
676 if (response.success) {
677 $('#rag-pdf-status').html('<span style="color:green;">✓ Complete</span>');
678 $('#rag-pdf-output').html(response.data.output);
679 $('#rag-pdf-files').val('');
680 } else {
681 $('#rag-pdf-status').html('<span style="color:red;">✗ Error</span>');
682 $('#rag-pdf-output').html('<p style="color:red;">' + response.data.message + '</p>');
683 }
684 },
685 error: function(xhr) {
686 $('#rag-pdf-submit').prop('disabled', false);
687 $('#rag-pdf-status').html('<span style="color:red;">✗ Error</span>');
688 $('#rag-pdf-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
689 }
690 });
691 });
692
693 // CSV Upload Handler
694 $('#rag-csv-form').on('submit', function(e) {
695 e.preventDefault();
696
697 var fileInput = $('#rag-csv-files')[0];
698 if (!fileInput.files.length) {
699 alert('Please select CSV files to upload');
700 return;
701 }
702
703 var formData = new FormData();
704 for (var i = 0; i < fileInput.files.length; i++) {
705 formData.append('rag_csv[]', fileInput.files[i]);
706 }
707 formData.append('action', 'rag_upload_csv');
708 formData.append('nonce', '<?php echo esc_js( wp_create_nonce('rag_upload_nonce') ); ?>');
709
710 $('#rag-csv-submit').prop('disabled', true);
711 $('#rag-csv-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
712 $('#rag-csv-output').html('');
713
714 $.ajax({
715 url: ajaxurl,
716 type: 'POST',
717 data: formData,
718 processData: false,
719 contentType: false,
720 success: function(response) {
721 $('#rag-csv-submit').prop('disabled', false);
722 if (response.success) {
723 $('#rag-csv-status').html('<span style="color:green;">✓ Complete</span>');
724 $('#rag-csv-output').html(response.data.output);
725 $('#rag-csv-files').val('');
726 } else {
727 $('#rag-csv-status').html('<span style="color:red;">✗ Error</span>');
728 $('#rag-csv-output').html('<p style="color:red;">' + response.data.message + '</p>');
729 }
730 },
731 error: function(xhr) {
732 $('#rag-csv-submit').prop('disabled', false);
733 $('#rag-csv-status').html('<span style="color:red;">✗ Error</span>');
734 $('#rag-csv-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
735 }
736 });
737 });
738
739 // XAML Upload Handler
740 $('#rag-xaml-form').on('submit', function(e) {
741 e.preventDefault();
742
743 var fileInput = $('#rag-xaml-files')[0];
744 if (!fileInput.files.length) {
745 alert('Please select XAML files to upload');
746 return;
747 }
748
749 var formData = new FormData();
750 for (var i = 0; i < fileInput.files.length; i++) {
751 formData.append('rag_xaml[]', fileInput.files[i]);
752 }
753 formData.append('action', 'rag_upload_xaml');
754 formData.append('nonce', '<?php echo esc_js( wp_create_nonce('rag_upload_nonce') ); ?>');
755
756 $('#rag-xaml-submit').prop('disabled', true);
757 $('#rag-xaml-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
758 $('#rag-xaml-output').html('');
759
760 $.ajax({
761 url: ajaxurl,
762 type: 'POST',
763 data: formData,
764 processData: false,
765 contentType: false,
766 success: function(response) {
767 $('#rag-xaml-submit').prop('disabled', false);
768 if (response.success) {
769 $('#rag-xaml-status').html('<span style="color:green;">✓ Complete</span>');
770 $('#rag-xaml-output').html(response.data.output);
771 $('#rag-xaml-files').val('');
772 } else {
773 $('#rag-xaml-status').html('<span style="color:red;">✗ Error</span>');
774 $('#rag-xaml-output').html('<p style="color:red;">' + response.data.message + '</p>');
775 }
776 },
777 error: function(xhr) {
778 $('#rag-xaml-submit').prop('disabled', false);
779 $('#rag-xaml-status').html('<span style="color:red;">✗ Error</span>');
780 $('#rag-xaml-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
781 }
782 });
783 });
784 });
785 </script>
786 <script>
787 jQuery(document).ready(function($) {
788 // Tab switching logic
789 $('.nav-tab-wrapper').on('click', '.nav-tab', function(e) {
790 var $tab = $(this);
791 var targetId = $tab.attr('href');
792 var $content = $(targetId);
793
794 if ($tab.data('disabled')) {
795 e.preventDefault();
796 return false;
797 }
798
799 if ($content.length) {
800 e.preventDefault();
801 // Update tabs
802 $('.nav-tab').removeClass('nav-tab-active');
803 $tab.addClass('nav-tab-active');
804
805 // Update content area
806 $('.qcld-tab-content').removeClass('active');
807 $content.addClass('active');
808
809 // Update URL hash without jumping
810 var compositeHash = '#ai-knowledge-base-tab' + targetId;
811 if (history.pushState) {
812 history.pushState(null, null, compositeHash);
813 } else {
814 window.location.hash = compositeHash;
815 }
816 }
817 });
818
819 // Handle hash on page load
820 var hash = window.location.hash;
821 if (hash && hash.includes('#rag-')) {
822 var subTabHash = '#' + hash.split('#').pop();
823 var $targetTab = $('.nav-tab-wrapper a[href="' + subTabHash + '"]');
824 if ($targetTab.length) {
825 $targetTab.trigger('click');
826 }
827 } else if (hash === '#ai-knowledge-base-tab') {
828 // Default to first tab if no sub-tab specified
829 $('.nav-tab-wrapper a').first().trigger('click');
830 }
831 });
832 </script>
833