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

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