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

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