PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 7.7.1
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v7.7.1
8.7.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 All 534 releases
chatbot / includes / admin / templates / rag.php

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

613 lines 26.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
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-database" class="nav-tab">KnowledgeBase Database</a>
21 </h2>
22
23 <div id="qcld-rag-settings-tab" class="qcld-tab-content active">
24 <!-- ===========================
25 EMBEDDING SOURCE OPTIONS
26 ============================ -->
27 <div class="wrap">
28 <h3>Choose Data Sources to Embed</h3>
29
30 <div class="mb-3">
31 <input type="checkbox" id="rag_embed_pages" <?php checked(get_option('rag_embed_pages'), '1'); ?>>
32 <label for="rag_embed_pages">Pages</label>
33 </div>
34
35 <div class="mb-3">
36 <input type="checkbox" id="rag_embed_posts" <?php checked(get_option('rag_embed_posts'), '1'); ?>>
37 <label for="rag_embed_posts">Posts</label>
38 </div>
39
40 <div class="mb-3">
41 <?php
42 $custom_post_types = get_post_types(['public' => true, '_builtin' => false], 'objects');
43 $selected_cpts = get_option('rag_embed_cpts', []);
44 ?>
45
46 <label><strong>Custom Post Types:</strong></label><br>
47
48 <div class="rag_embed_cpts_wrapper">
49 <?php foreach ($custom_post_types as $cpt): ?>
50 <div class="rag_cpt_checkbox">
51 <input type="checkbox" class="rag_embed_cpts_checkbox"
52 id="rag_cpt_<?php echo esc_attr($cpt->name); ?>"
53 value="<?php echo esc_attr($cpt->name); ?>"
54 <?php echo in_array($cpt->name, $selected_cpts) ? 'checked' : ''; ?>>
55 <label for="rag_cpt_<?php echo esc_attr($cpt->name); ?>"><?php echo esc_html($cpt->label); ?></label>
56 </div>
57 <?php endforeach; ?>
58 </div>
59 <p class="description">Select multiple custom post types to embed.</p>
60 </div>
61
62
63 </div>
64 <!-- ===========================
65 EXECUTION BUTTON
66 ============================ -->
67 <div class="wrap my-4">
68 <form method="post" id="rag_embed_form">
69 <input type="hidden" name="embed_all_sources" value="1">
70 <button type="button" id="rag_embed_btn" class="button button-primary">Embed All Selected Sources</button>
71 </form>
72
73 <?php if (isset($_POST['embed_all_sources'])): ?>
74 <h3>Embedding started...</h3>
75 <?php qcld_wpopenai_addons::instance()->wp_rag_embed_all_sources(); ?>
76 <?php endif; ?>
77 </div>
78 <!-- ===========================
79 SAVE SETTINGS BUTTON
80 ============================ -->
81 <div class="wrap">
82 <button class="qcld-btn-primary" id="save_rag_setting">Save Settings</button>
83 </div>
84 </div>
85
86
87 <div id="rag-sync" class="qcld-tab-content">
88 <?php if ( $wpchatbot_license_valid != 'master' && $wpchatbot_license_valid != 'professional'): ?>
89 <div class="wrap">
90 <div style="background-color: #fee; border: 1px solid #c33; padding: 15px; margin: 20px 0; border-radius: 4px;">
91 <p style="color: #c33; font-weight: bold; margin: 0; font-size: 14px;">
92 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
93 </p>
94 </div>
95 </div>
96 <?php endif; ?>
97
98 <div style="<?php if ( $wpchatbot_license_valid != 'master' && $wpchatbot_license_valid != 'professional'){ echo 'opacity:0.5; pointer-events:none;'; } ?>">
99 <!-- ===========================
100 SYNC SETTINGS
101 ============================ -->
102 <div class="wrap">
103 <h3>Sync Settings</h3>
104
105 <div class="mb-3">
106 <input type="checkbox" id="rag_auto_sync_enabled" <?php checked(get_option('rag_auto_sync_enabled'), '1'); ?>>
107 <label for="rag_auto_sync_enabled"><strong>Enable Auto Sync (on Save)</strong></label>
108 <p class="description">Automatically update embeddings when a post or product is saved/updated.</p>
109 </div>
110
111 </div>
112
113
114 <!-- ===========================
115 PDF UPLOAD (AJAX)
116 ============================ -->
117 <div class="wrap">
118 <h3>Upload PDF for RAG</h3>
119
120 <form id="rag-pdf-form">
121 <input type="file" id="rag-pdf-files" name="rag_pdf[]" multiple accept="application/pdf" />
122 <br>
123 <button type="submit" class="button button-primary" id="rag-pdf-submit">Upload & Embed PDF</button>
124 <span id="rag-pdf-status" style="margin-left: 10px;"></span>
125 </form>
126
127 <div id="rag-pdf-output" style="margin-top: 15px;"></div>
128 </div>
129
130 <!-- ===========================
131 CSV UPLOAD (AJAX)
132 ============================ -->
133 <div class="wrap">
134 <h3>Upload CSV Data for RAG</h3>
135 <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>
136
137 <form id="rag-csv-form">
138 <input type="file" id="rag-csv-files" name="rag_csv[]" multiple accept=".csv,text/csv" />
139 <br>
140 <button type="submit" class="button button-primary" id="rag-csv-submit">Upload & Embed CSV</button>
141 <span id="rag-csv-status" style="margin-left: 10px;"></span>
142 </form>
143
144 <div id="rag-csv-output" style="margin-top: 15px;"></div>
145 </div>
146
147 <!-- ===========================
148 XAML UPLOAD (AJAX)
149 ============================ -->
150 <div class="wrap">
151 <h3>Upload XML Data for RAG</h3>
152 <p>Upload XML files with data to be embedded.</p>
153
154 <form id="rag-xaml-form">
155 <input type="file" id="rag-xaml-files" name="rag_xaml[]" multiple accept=".xaml,text/xml,application/xml" />
156 <br>
157 <button type="submit" class="button button-primary" id="rag-xaml-submit">Upload & Embed XAML</button>
158 <span id="rag-xaml-status" style="margin-left: 10px;"></span>
159 </form>
160
161 <div id="rag-xaml-output" style="margin-top: 15px;"></div>
162 </div>
163
164 <!-- ===========================
165 SITEMAP SUBMISSION
166 ============================ -->
167 <div class="wrap">
168 <h3>Submit Sitemap for RAG</h3>
169 <p>Enter your XML Sitemap URL to crawl and embed all pages.</p>
170 <input type="url" id="botmaster_sitemap_url" class="regular-text" placeholder="https://example.com/sitemap.xml" style="width: 100%; max-width: 400px;">
171 <button type="button" id="botmaster_submit_sitemap_btn" class="button button-primary">Process Sitemap</button>
172 <div id="botmaster_sitemap_status" style="margin-top: 10px;"></div>
173 </div>
174 </div>
175
176 <div class="wrap">
177 <button class="qcld-btn-primary" id="save_rag_setting">Save Settings</button>
178 </div>
179 </div>
180
181 <style>
182 .qcld-tab-content {
183 display: none !important;
184 }
185
186 .qcld-tab-content.active {
187 display: block !important;
188 }
189 </style>
190
191 <script>
192 jQuery(document).ready(function($) {
193 $('#botmaster_submit_sitemap_btn').on('click', function() {
194 var sitemapUrl = $('#botmaster_sitemap_url').val();
195 var statusDiv = $('#botmaster_sitemap_status');
196
197 if (!sitemapUrl) {
198 alert('Please enter a Sitemap URL');
199 return;
200 }
201
202 statusDiv.html('Processing... please wait.');
203 $(this).prop('disabled', true);
204
205 $.post(ajaxurl, {
206 action: 'botmaster_submit_sitemap',
207 sitemap_url: sitemapUrl,
208 nonce: '<?php echo wp_create_nonce("botmaster_kb_nonce"); ?>'
209 }, function(response) {
210 $('#botmaster_submit_sitemap_btn').prop('disabled', false);
211 if (response.success) {
212 statusDiv.html('<div class="notice notice-success inline"><p>' + response.data.message + '</p></div>');
213 } else {
214 statusDiv.html('<div class="notice notice-error inline"><p>Error: ' + response.data + '</p></div>');
215 }
216 });
217 });
218 });
219 </script>
220
221
222
223
224
225 <div id="rag-database" class="qcld-tab-content">
226 <!-- ===========================
227 KNOWLEDGE BASE MANAGEMENT
228 ============================ -->
229 <div class="wrap">
230 <h3>Knowledge Base</h3>
231
232 <div class="tablenav top">
233 <div class="alignleft actions bulkactions">
234 <select id="rag-bulk-action-selector">
235 <option value="-1">Bulk Actions</option>
236 <option value="delete">Delete</option>
237 </select>
238 <button type="button" id="rag-apply-bulk-action" class="button action">Apply</button>
239 </div>
240 <div class="alignleft actions">
241 <button type="button" id="rag-delete-all" class="button button-link-delete" style="margin-left: 10px;">Delete All</button>
242
243
244 <div class="alignright actions" style="margin-left: 10px;">
245 <form method="get" style="display:inline-block;" action="?page=wpbot_openAi#ai-knowledge-base-tab#rag-database">
246 <input type="hidden" name="page" value="<?php echo esc_attr($_GET['page']); ?>">
247 <?php if (isset($_GET['post_type'])): ?>
248 <input type="hidden" name="post_type" value="<?php echo esc_attr($_GET['post_type']); ?>">
249 <?php endif; ?>
250 <p class="search-box" style="margin:0;">
251 <input type="search" id="rag-search-input" name="s" value="<?php echo esc_attr(isset($_GET['s']) ? $_GET['s'] : ''); ?>" placeholder="Search documents...">
252 <input type="submit" id="search-submit" class="button" value="Search">
253 </p>
254 </form>
255 </div>
256 </div>
257
258 <br class="clear">
259 </div>
260 <?php
261 global $wpdb;
262 $table_rag_documents = $wpdb->prefix . 'rag_documents';
263
264 // Handle Table Creation
265 if (isset($_POST['rag_create_table']) && check_admin_referer('rag_create_table_nonce')) {
266 $charset = $wpdb->get_charset_collate();
267 $sql_rag_documents = "CREATE TABLE $table_rag_documents (
268 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
269 doc_id VARCHAR(100) DEFAULT NULL,
270 title VARCHAR(255) NOT NULL,
271 content LONGTEXT NOT NULL,
272 embedding LONGTEXT NOT NULL,
273 source_type VARCHAR(20) DEFAULT 'post',
274 source_url VARCHAR(255) DEFAULT NULL,
275 file_url TEXT DEFAULT NULL,
276 metadata LONGTEXT DEFAULT NULL,
277 status VARCHAR(50) DEFAULT 'complete',
278 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
279 ) $charset;";
280 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
281 dbDelta($sql_rag_documents);
282 echo '<div class="notice notice-success inline"><p>Database table created successfully.</p></div>';
283 }
284
285 // Check if table exists
286 $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_rag_documents'") === $table_rag_documents;
287
288 if (!$table_exists) {
289 ?>
290 <div class="wrap">
291 <h3>Knowledge Base Database</h3>
292 <div class="notice notice-warning inline">
293 <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>
294 </div>
295 <form method="post">
296 <?php wp_nonce_field('rag_create_table_nonce'); ?>
297 <input type="hidden" name="rag_create_table" value="1">
298 <button type="submit" class="button button-primary">Create Database Table</button>
299 </form>
300 </div>
301 <?php
302 } else {
303 ?>
304 <?php
305 $table_name = $table_rag_documents;
306
307 $search_query = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
308 $where_clause = '';
309 if ($search_query) {
310 $where_clause = $wpdb->prepare(" WHERE title LIKE %s OR content LIKE %s ", '%' . $wpdb->esc_like($search_query) . '%', '%' . $wpdb->esc_like($search_query) . '%');
311 }
312
313 $total_items = $wpdb->get_var("SELECT COUNT(id) FROM $table_name $where_clause");
314 $items_per_page = 50;
315 $page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
316 $offset = ($page - 1) * $items_per_page;
317 $total_pages = ceil($total_items / $items_per_page);
318
319 $pagination_args = array(
320 'base' => add_query_arg(array('paged' => '%#%', 's' => $search_query), '?page=wpbot_openAi') . '#ai-knowledge-base-tab#rag-database',
321 'format' => '',
322 'prev_text' => __('&laquo;'),
323 'next_text' => __('&raquo;'),
324 'total' => $total_pages,
325 'current' => $page,
326 'type' => 'plain',
327 );
328
329 if ($total_pages > 1) {
330 echo '<div class="tablenav-pages"><span class="displaying-num">' . sprintf(_n('%s item', '%s items', $total_items), number_format_i18n($total_items)) . '</span>';
331 echo paginate_links($pagination_args);
332 echo '</div>';
333 }
334 ?>
335
336 <table class="wp-list-table widefat fixed striped">
337 <thead>
338 <tr>
339 <td id="cb" class="manage-column column-cb check-column">
340 <input type="checkbox" id="rag-select-all">
341 </td>
342 <th>Title</th>
343 <th>Content</th>
344 <th>Source Type</th>
345 <th>URL/File</th>
346 <th>Status</th>
347 <th>Actions</th>
348 </tr>
349 </thead>
350 <tbody id="rag-knowledge-base-list">
351 <?php
352 $documents = $wpdb->get_results($wpdb->prepare("SELECT * FROM $table_name $where_clause ORDER BY created_at DESC LIMIT %d OFFSET %d", $items_per_page, $offset));
353
354 if ($documents) {
355 foreach ($documents as $doc) {
356 ?>
357 <tr id="rag-doc-<?php echo $doc->id; ?>">
358 <th scope="qcld-row" class="check-column">
359 <input type="checkbox" class="rag-doc-checkbox" value="<?php echo $doc->id; ?>">
360 </th>
361 <td><?php echo esc_html($doc->title); ?></td>
362 <td>
363 <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;">
364 <?php echo wp_kses_post($doc->content); ?>
365 </div>
366 </td>
367 <td><?php echo esc_html($doc->source_type); ?></td>
368 <td><?php echo esc_html($doc->source_url ?: $doc->file_url); ?></td>
369 <td><?php echo esc_html($doc->status); ?></td>
370 <td>
371 <?php if (!in_array($doc->source_type, ['csv', 'xml', 'xaml','sitemap'])): ?>
372 <button class="button button-small rag-sync-doc" data-id="<?php echo $doc->id; ?>" title="Re-sync data from source">Sync</button>
373 <?php endif; ?>
374 <button class="button button-small rag-edit-doc" data-id="<?php echo $doc->id; ?>">Edit</button>
375 <button class="button button-small button-link-delete rag-delete-doc" data-id="<?php echo $doc->id; ?>">Delete</button>
376 </td>
377 </tr>
378 <?php
379 }
380 } else {
381 echo '<tr><td colspan="7">No documents found in knowledge base.</td></tr>';
382 }
383 ?>
384 </tbody>
385 </table>
386
387 <div class="tablenav bottom">
388 <?php
389 if ($total_pages > 1) {
390 echo '<div class="tablenav-pages"><span class="displaying-num">' . sprintf(_n('%s item', '%s items', $total_items), number_format_i18n($total_items)) . '</span>';
391 echo paginate_links($pagination_args);
392 echo '</div>';
393 }
394 ?>
395 <br class="clear">
396 </div>
397 <?php } ?>
398 </div>
399 </div>
400
401 <!-- Edit Document Modal -->
402 <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);">
403 <div style="background:#fff; margin:10% auto; padding:20px; width:60%; border-radius:5px;">
404 <h4>Edit Knowledge Base Document</h4>
405 <input type="hidden" id="edit-doc-id">
406 <div class="mb-3">
407 <label>Title</label><br>
408 <input type="text" id="edit-doc-title" class="regular-text" style="width:100%;">
409 </div>
410 <div class="mb-3">
411 <label>Content</label><br>
412 <textarea id="edit-doc-content" rows="10" style="width:100%;"></textarea>
413 </div>
414 <p class="description">Note: Updating content will re-generate embeddings.</p>
415 <div class="mt-3">
416 <button id="save-edit-doc" class="button button-primary">Save Changes</button>
417 <button id="close-edit-modal" class="button">Cancel</button>
418 </div>
419 </div>
420 </div>
421 </div>
422
423
424
425 <script>
426 jQuery(document).ready(function($) {
427 // PDF Upload Handler
428 $('#rag-pdf-form').on('submit', function(e) {
429 e.preventDefault();
430
431 var fileInput = $('#rag-pdf-files')[0];
432 if (!fileInput.files.length) {
433 alert('Please select PDF files to upload');
434 return;
435 }
436
437 var formData = new FormData();
438 for (var i = 0; i < fileInput.files.length; i++) {
439 formData.append('rag_pdf[]', fileInput.files[i]);
440 }
441 formData.append('action', 'rag_upload_pdf');
442 formData.append('nonce', '<?php echo wp_create_nonce('rag_upload_nonce'); ?>');
443
444 $('#rag-pdf-submit').prop('disabled', true);
445 $('#rag-pdf-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
446 $('#rag-pdf-output').html('');
447
448 $.ajax({
449 url: ajaxurl,
450 type: 'POST',
451 data: formData,
452 processData: false,
453 contentType: false,
454 success: function(response) {
455 $('#rag-pdf-submit').prop('disabled', false);
456 if (response.success) {
457 $('#rag-pdf-status').html('<span style="color:green;">✓ Complete</span>');
458 $('#rag-pdf-output').html(response.data.output);
459 $('#rag-pdf-files').val('');
460 } else {
461 $('#rag-pdf-status').html('<span style="color:red;">✗ Error</span>');
462 $('#rag-pdf-output').html('<p style="color:red;">' + response.data.message + '</p>');
463 }
464 },
465 error: function(xhr) {
466 $('#rag-pdf-submit').prop('disabled', false);
467 $('#rag-pdf-status').html('<span style="color:red;">✗ Error</span>');
468 $('#rag-pdf-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
469 }
470 });
471 });
472
473 // CSV Upload Handler
474 $('#rag-csv-form').on('submit', function(e) {
475 e.preventDefault();
476
477 var fileInput = $('#rag-csv-files')[0];
478 if (!fileInput.files.length) {
479 alert('Please select CSV files to upload');
480 return;
481 }
482
483 var formData = new FormData();
484 for (var i = 0; i < fileInput.files.length; i++) {
485 formData.append('rag_csv[]', fileInput.files[i]);
486 }
487 formData.append('action', 'rag_upload_csv');
488 formData.append('nonce', '<?php echo wp_create_nonce('rag_upload_nonce'); ?>');
489
490 $('#rag-csv-submit').prop('disabled', true);
491 $('#rag-csv-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
492 $('#rag-csv-output').html('');
493
494 $.ajax({
495 url: ajaxurl,
496 type: 'POST',
497 data: formData,
498 processData: false,
499 contentType: false,
500 success: function(response) {
501 $('#rag-csv-submit').prop('disabled', false);
502 if (response.success) {
503 $('#rag-csv-status').html('<span style="color:green;">✓ Complete</span>');
504 $('#rag-csv-output').html(response.data.output);
505 $('#rag-csv-files').val('');
506 } else {
507 $('#rag-csv-status').html('<span style="color:red;">✗ Error</span>');
508 $('#rag-csv-output').html('<p style="color:red;">' + response.data.message + '</p>');
509 }
510 },
511 error: function(xhr) {
512 $('#rag-csv-submit').prop('disabled', false);
513 $('#rag-csv-status').html('<span style="color:red;">✗ Error</span>');
514 $('#rag-csv-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
515 }
516 });
517 });
518
519 // XAML Upload Handler
520 $('#rag-xaml-form').on('submit', function(e) {
521 e.preventDefault();
522
523 var fileInput = $('#rag-xaml-files')[0];
524 if (!fileInput.files.length) {
525 alert('Please select XAML files to upload');
526 return;
527 }
528
529 var formData = new FormData();
530 for (var i = 0; i < fileInput.files.length; i++) {
531 formData.append('rag_xaml[]', fileInput.files[i]);
532 }
533 formData.append('action', 'rag_upload_xaml');
534 formData.append('nonce', '<?php echo wp_create_nonce('rag_upload_nonce'); ?>');
535
536 $('#rag-xaml-submit').prop('disabled', true);
537 $('#rag-xaml-status').html('<span class="spinner is-active" style="float:none;"></span> Uploading and processing...');
538 $('#rag-xaml-output').html('');
539
540 $.ajax({
541 url: ajaxurl,
542 type: 'POST',
543 data: formData,
544 processData: false,
545 contentType: false,
546 success: function(response) {
547 $('#rag-xaml-submit').prop('disabled', false);
548 if (response.success) {
549 $('#rag-xaml-status').html('<span style="color:green;">✓ Complete</span>');
550 $('#rag-xaml-output').html(response.data.output);
551 $('#rag-xaml-files').val('');
552 } else {
553 $('#rag-xaml-status').html('<span style="color:red;">✗ Error</span>');
554 $('#rag-xaml-output').html('<p style="color:red;">' + response.data.message + '</p>');
555 }
556 },
557 error: function(xhr) {
558 $('#rag-xaml-submit').prop('disabled', false);
559 $('#rag-xaml-status').html('<span style="color:red;">✗ Error</span>');
560 $('#rag-xaml-output').html('<p style="color:red;">Upload failed. Please try again.</p>');
561 }
562 });
563 });
564 });
565 </script>
566 <script>
567 jQuery(document).ready(function($) {
568 // Tab switching logic
569 $('.nav-tab-wrapper').on('click', '.nav-tab', function(e) {
570 var $tab = $(this);
571 var targetId = $tab.attr('href');
572 var $content = $(targetId);
573
574 if ($tab.data('disabled')) {
575 e.preventDefault();
576 return false;
577 }
578
579 if ($content.length) {
580 e.preventDefault();
581 // Update tabs
582 $('.nav-tab').removeClass('nav-tab-active');
583 $tab.addClass('nav-tab-active');
584
585 // Update content area
586 $('.qcld-tab-content').removeClass('active');
587 $content.addClass('active');
588
589 // Update URL hash without jumping
590 var compositeHash = '#ai-knowledge-base-tab' + targetId;
591 if (history.pushState) {
592 history.pushState(null, null, compositeHash);
593 } else {
594 window.location.hash = compositeHash;
595 }
596 }
597 });
598
599 // Handle hash on page load
600 var hash = window.location.hash;
601 if (hash && hash.includes('#rag-')) {
602 var subTabHash = '#' + hash.split('#').pop();
603 var $targetTab = $('.nav-tab-wrapper a[href="' + subTabHash + '"]');
604 if ($targetTab.length) {
605 $targetTab.trigger('click');
606 }
607 } else if (hash === '#ai-knowledge-base-tab') {
608 // Default to first tab if no sub-tab specified
609 $('.nav-tab-wrapper a').first().trigger('click');
610 }
611 });
612 </script>
613