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

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

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