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

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

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