PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.4.9
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.4.9
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.4.9, at includes/admin/templates/rag.php

831 lines 45.6 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;'),
536 'next_text' => __('&raquo;'),
537 'total' => $total_pages,
538 'current' => $page,
539 'type' => 'plain',
540 );
541
542 if ($total_pages > 1) {
543 echo '<div class="tablenav-pages"><span class="displaying-num">' . sprintf( esc_html( _n('%s item', '%s items', $total_items) ), esc_html( number_format_i18n($total_items) ) ) . '</span>';
544 echo wp_kses_post( paginate_links($pagination_args) );
545 echo '</div>';
546 }
547 ?>
548
549 <table class="wp-list-table widefat fixed striped">
550 <thead>
551 <tr>
552 <td id="cb" class="manage-column column-cb check-column">
553 <input type="checkbox" id="rag-select-all">
554 </td>
555 <th>Title</th>
556 <th>Content</th>
557 <th>Source Type</th>
558 <th>URL/File</th>
559 <th>Status</th>
560 <th>Actions</th>
561 </tr>
562 </thead>
563 <tbody id="rag-knowledge-base-list">
564 <?php
565 if ($search_query) {
566 $like = '%' . $wpdb->esc_like($search_query) . '%';
567 $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
568 } else {
569 $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
570 }
571
572 if ($documents) {
573 foreach ($documents as $doc) {
574 ?>
575 <tr id="rag-doc-<?php echo esc_attr( $doc->id ); ?>">
576 <th scope="qcld-row" class="check-column">
577 <input type="checkbox" class="rag-doc-checkbox" value="<?php echo esc_attr( $doc->id ); ?>">
578 </th>
579 <td><?php echo esc_html($doc->title); ?></td>
580 <td>
581 <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;">
582 <?php echo wp_kses_post($doc->content); ?>
583 </div>
584 </td>
585 <td><?php echo esc_html($doc->source_type); ?></td>
586 <td><?php echo esc_html($doc->source_url ?: $doc->file_url); ?></td>
587 <td><?php echo esc_html($doc->status); ?></td>
588 <td>
589 <?php if (!in_array($doc->source_type, ['csv', 'xml', 'xaml','sitemap'])): ?>
590 <button class="button button-small rag-sync-doc" data-id="<?php echo esc_attr( $doc->id ); ?>" title="Re-sync data from source">Sync</button>
591 <?php endif; ?>
592 <button class="button button-small rag-edit-doc" data-id="<?php echo esc_attr( $doc->id ); ?>">Edit</button>
593 <button class="button button-small button-link-delete rag-delete-doc" data-id="<?php echo esc_attr( $doc->id ); ?>">Delete</button>
594 </td>
595 </tr>
596 <?php
597 }
598 } else {
599 echo '<tr><td colspan="7">No documents found in knowledge base.</td></tr>';
600 }
601 ?>
602 </tbody>
603 </table>
604
605 <div class="tablenav bottom">
606 <?php
607 if ($total_pages > 1) {
608 echo '<div class="tablenav-pages"><span class="displaying-num">' . sprintf( esc_html( _n('%s item', '%s items', $total_items) ), esc_html( number_format_i18n($total_items) ) ) . '</span>';
609 echo wp_kses_post( paginate_links($pagination_args) );
610 echo '</div>';
611 }
612 ?>
613 <br class="clear">
614 </div>
615 <?php } ?>
616 </div>
617 </div>
618
619 <!-- Edit Document Modal -->
620 <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);">
621 <div style="background:#fff; margin:10% auto; padding:20px; width:60%; border-radius:5px;">
622 <h4>Edit Knowledge Base Document</h4>
623 <input type="hidden" id="edit-doc-id">
624 <div class="mb-3">
625 <label>Title</label><br>
626 <input type="text" id="edit-doc-title" class="regular-text" style="width:100%;">
627 </div>
628 <div class="mb-3">
629 <label>Content</label><br>
630 <textarea id="edit-doc-content" rows="10" style="width:100%;"></textarea>
631 </div>
632 <p class="description">Note: Updating content will re-generate embeddings.</p>
633 <div class="mt-3">
634 <button id="save-edit-doc" class="button button-primary">Save Changes</button>
635 <button id="close-edit-modal" class="button">Cancel</button>
636 </div>
637 </div>
638 </div>
639 </div>
640
641
642
643 <script>
644 jQuery(document).ready(function($) {
645 // PDF Upload Handler
646 $('#rag-pdf-form').on('submit', function(e) {
647 e.preventDefault();
648
649 var fileInput = $('#rag-pdf-files')[0];
650 if (!fileInput.files.length) {
651 alert('Please select PDF files to upload');
652 return;
653 }
654
655 var formData = new FormData();
656 for (var i = 0; i < fileInput.files.length; i++) {
657 formData.append('rag_pdf[]', fileInput.files[i]);
658 }
659 formData.append('action', 'rag_upload_pdf');
660 formData.append('nonce', '<?php echo esc_js( wp_create_nonce('rag_upload_nonce') ); ?>');
661
662 $('#rag-pdf-submit').prop('disabled', true);
663 $('#rag-pdf-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
664 $('#rag-pdf-output').html('');
665
666 $.ajax({
667 url: ajaxurl,
668 type: 'POST',
669 data: formData,
670 processData: false,
671 contentType: false,
672 success: function(response) {
673 $('#rag-pdf-submit').prop('disabled', false);
674 if (response.success) {
675 $('#rag-pdf-status').html('<span style="color:green;">✓ Complete</span>');
676 $('#rag-pdf-output').html(response.data.output);
677 $('#rag-pdf-files').val('');
678 } else {
679 $('#rag-pdf-status').html('<span style="color:red;">✗ Error</span>');
680 $('#rag-pdf-output').html('<p style="color:red;">' + response.data.message + '</p>');
681 }
682 },
683 error: function(xhr) {
684 $('#rag-pdf-submit').prop('disabled', false);
685 $('#rag-pdf-status').html('<span style="color:red;">✗ Error</span>');
686 $('#rag-pdf-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
687 }
688 });
689 });
690
691 // CSV Upload Handler
692 $('#rag-csv-form').on('submit', function(e) {
693 e.preventDefault();
694
695 var fileInput = $('#rag-csv-files')[0];
696 if (!fileInput.files.length) {
697 alert('Please select CSV files to upload');
698 return;
699 }
700
701 var formData = new FormData();
702 for (var i = 0; i < fileInput.files.length; i++) {
703 formData.append('rag_csv[]', fileInput.files[i]);
704 }
705 formData.append('action', 'rag_upload_csv');
706 formData.append('nonce', '<?php echo esc_js( wp_create_nonce('rag_upload_nonce') ); ?>');
707
708 $('#rag-csv-submit').prop('disabled', true);
709 $('#rag-csv-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
710 $('#rag-csv-output').html('');
711
712 $.ajax({
713 url: ajaxurl,
714 type: 'POST',
715 data: formData,
716 processData: false,
717 contentType: false,
718 success: function(response) {
719 $('#rag-csv-submit').prop('disabled', false);
720 if (response.success) {
721 $('#rag-csv-status').html('<span style="color:green;">✓ Complete</span>');
722 $('#rag-csv-output').html(response.data.output);
723 $('#rag-csv-files').val('');
724 } else {
725 $('#rag-csv-status').html('<span style="color:red;">✗ Error</span>');
726 $('#rag-csv-output').html('<p style="color:red;">' + response.data.message + '</p>');
727 }
728 },
729 error: function(xhr) {
730 $('#rag-csv-submit').prop('disabled', false);
731 $('#rag-csv-status').html('<span style="color:red;">✗ Error</span>');
732 $('#rag-csv-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
733 }
734 });
735 });
736
737 // XAML Upload Handler
738 $('#rag-xaml-form').on('submit', function(e) {
739 e.preventDefault();
740
741 var fileInput = $('#rag-xaml-files')[0];
742 if (!fileInput.files.length) {
743 alert('Please select XAML files to upload');
744 return;
745 }
746
747 var formData = new FormData();
748 for (var i = 0; i < fileInput.files.length; i++) {
749 formData.append('rag_xaml[]', fileInput.files[i]);
750 }
751 formData.append('action', 'rag_upload_xaml');
752 formData.append('nonce', '<?php echo esc_js( wp_create_nonce('rag_upload_nonce') ); ?>');
753
754 $('#rag-xaml-submit').prop('disabled', true);
755 $('#rag-xaml-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
756 $('#rag-xaml-output').html('');
757
758 $.ajax({
759 url: ajaxurl,
760 type: 'POST',
761 data: formData,
762 processData: false,
763 contentType: false,
764 success: function(response) {
765 $('#rag-xaml-submit').prop('disabled', false);
766 if (response.success) {
767 $('#rag-xaml-status').html('<span style="color:green;">✓ Complete</span>');
768 $('#rag-xaml-output').html(response.data.output);
769 $('#rag-xaml-files').val('');
770 } else {
771 $('#rag-xaml-status').html('<span style="color:red;">✗ Error</span>');
772 $('#rag-xaml-output').html('<p style="color:red;">' + response.data.message + '</p>');
773 }
774 },
775 error: function(xhr) {
776 $('#rag-xaml-submit').prop('disabled', false);
777 $('#rag-xaml-status').html('<span style="color:red;">✗ Error</span>');
778 $('#rag-xaml-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
779 }
780 });
781 });
782 });
783 </script>
784 <script>
785 jQuery(document).ready(function($) {
786 // Tab switching logic
787 $('.nav-tab-wrapper').on('click', '.nav-tab', function(e) {
788 var $tab = $(this);
789 var targetId = $tab.attr('href');
790 var $content = $(targetId);
791
792 if ($tab.data('disabled')) {
793 e.preventDefault();
794 return false;
795 }
796
797 if ($content.length) {
798 e.preventDefault();
799 // Update tabs
800 $('.nav-tab').removeClass('nav-tab-active');
801 $tab.addClass('nav-tab-active');
802
803 // Update content area
804 $('.qcld-tab-content').removeClass('active');
805 $content.addClass('active');
806
807 // Update URL hash without jumping
808 var compositeHash = '#ai-knowledge-base-tab' + targetId;
809 if (history.pushState) {
810 history.pushState(null, null, compositeHash);
811 } else {
812 window.location.hash = compositeHash;
813 }
814 }
815 });
816
817 // Handle hash on page load
818 var hash = window.location.hash;
819 if (hash && hash.includes('#rag-')) {
820 var subTabHash = '#' + hash.split('#').pop();
821 var $targetTab = $('.nav-tab-wrapper a[href="' + subTabHash + '"]');
822 if ($targetTab.length) {
823 $targetTab.trigger('click');
824 }
825 } else if (hash === '#ai-knowledge-base-tab') {
826 // Default to first tab if no sub-tab specified
827 $('.nav-tab-wrapper a').first().trigger('click');
828 }
829 });
830 </script>
831