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
← All changes | includes/admin/templates/rag.php +827 -845 8.5.88.3.8 View file →
@@ -1,845 +1,827 @@
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>
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>