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

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