PageSourceSearch

https://sardarpateleducation.com/wp-content/plugins/sardar-patel-ai-chatbot/public/js/chatbot.js?ver=2.0.0

js sardarpateleducation.com collected 2026-10-07 00:16:56 UTC 9,855 bytes, 341 lines download raw bytes

1/**
2 * Sardar Patel AI Counselling Chatbot Frontend JS Script
3 */
4
5jQuery(document).ready(function($) {
6	// Check if container exists
7	if (!$('#spai-chatbot-container').length) {
8		return;
9	}
10
11	// State Variables
12	const restUrl = spaiSettings.restUrl;
13	const nonce = spaiSettings.nonce;
14	const sessionKey = spaiSettings.sessionKey;
15	let sessionId = localStorage.getItem(sessionKey);
16	
17	if (!sessionId) {
18		sessionId = 'spai_' + Math.random().toString(36).substring(2, 15) + '_' + Date.now();
19		localStorage.setItem(sessionKey, sessionId);
20	}
21
22	// Capture UTM Parameters
23	const getUtmParams = () => {
24		const urlParams = new URLSearchParams(window.location.search);
25		return {
26			page_url: window.location.href,
27			referrer: document.referrer || '',
28			utm_source: urlParams.get('utm_source') || '',
29			utm_medium: urlParams.get('utm_medium') || '',
30			utm_campaign: urlParams.get('utm_campaign') || '',
31			utm_term: urlParams.get('utm_term') || '',
32			utm_content: urlParams.get('utm_content') || ''
33		};
34	};
35
36	const utmParams = getUtmParams();
37
38	// Elements
39	const $launcher = $('#spai-chatbot-launcher');
40	const $panel = $('#spai-chatbot-panel');
41	const $closeBtn = $('#spai-chatbot-close');
42	const $input = $('#spai-chat-input');
43	const $sendBtn = $('#spai-chat-send');
44	const $body = $('#spai-chat-body');
45	const $messagesList = $('.spai-messages-list');
46	const $typingIndicator = $('.spai-typing-indicator');
47	const $consentCheckbox = $('#spai-consent-checkbox');
48
49	// Toggle Open/Close
50	if ($launcher.length) {
51		$launcher.on('click', function() {
52			const isOpen = $panel.hasClass('open');
53			if (isOpen) {
54				closeChat();
55			} else {
56				openChat();
57			}
58		});
59	}
60
61	$closeBtn.on('click', closeChat);
62
63	function openChat() {
64		$panel.addClass('open').show().attr('aria-hidden', 'false');
65		$launcher.addClass('active').attr('aria-expanded', 'true');
66		$input.focus();
67		scrollToBottom();
68		
69		// If empty, send a silent init trigger to load greeting or resume
70		if ($messagesList.children().length <= 1) {
71			loadChatHistory();
72		}
73	}
74
75	function closeChat() {
76		$panel.removeClass('open').hide().attr('aria-hidden', 'true');
77		$launcher.removeClass('active').attr('aria-expanded', 'false');
78	}
79
80	// Auto-expand input textarea
81	$input.on('input', function() {
82		this.style.height = 'auto';
83		this.style.height = (this.scrollHeight - 10) + 'px';
84		if ($(this).val().trim() !== '') {
85			$sendBtn.addClass('active');
86		} else {
87			$sendBtn.removeClass('active');
88		}
89	});
90
91	// Enter Key triggers send
92	$input.on('keydown', function(e) {
93		if (e.key === 'Enter' && !e.shiftKey) {
94			e.preventDefault();
95			sendMessage();
96		}
97	});
98
99	$sendBtn.on('click', sendMessage);
100
101	// Quick Reply buttons click
102	$(document).on('click', '.spai-quick-reply-btn', function() {
103		const text = $(this).text().trim();
104		const intent = $(this).data('intent');
105		sendMessage(text, { intent: intent });
106	});
107
108	// Choices buttons click (inside bubbles)
109	$(document).on('click', '.spai-choice-btn', function() {
110		if ($(this).is('a')) {
111			return; // Let standard link redirect fire
112		}
113		const text = $(this).text().trim();
114		const value = $(this).data('value');
115		const type = $(this).data('type'); // 'counselling_mode', 'slot', etc.
116
117		// Disable all buttons in this bubble to prevent double clicks
118		$(this).parent().find('.spai-choice-btn').attr('disabled', 'disabled');
119		$(this).addClass('selected');
120
121		sendMessage(text, { 
122			choice_value: value,
123			choice_type: type 
124		});
125	});
126
127	// Main Send Message Function
128	function sendMessage(customText = '', meta = {}) {
129		const text = customText !== '' ? customText : $input.val().trim();
130		if (text === '') return;
131
132		// Verify GDPR consent if lead collection is in progress
133		if (meta.choice_type === 'consent' && !$consentCheckbox.is(':checked')) {
134			alert('Please check the consent box to proceed.');
135			return;
136		}
137
138		// Clear input
139		if (customText === '') {
140			$input.val('');
141			$input.css('height', 'auto');
142			$sendBtn.removeClass('active');
143		}
144
145		// Append User Message to UI
146		appendMessage(text, 'user');
147		scrollToBottom();
148
149		// Show Typing Indicator
150		showTyping();
151
152		// AJAX Request to Chatbot API
153		const payload = $.extend({
154			session_id: sessionId,
155			message: text,
156			nonce: nonce
157		}
157, utmParams, meta);
158
159		$.ajax({
160			url: restUrl + 'chat',
161			method: 'POST',
162			beforeSend: function(xhr) {
163				xhr.setRequestHeader('X-WP-Nonce', nonce);
164			},
165			contentType: 'application/json',
166			data: JSON.stringify(payload),
167			success: function(response) {
168				hideTyping();
169				if (response.success && response.data) {
170					processBotResponse(response.data);
171				} else {
172					appendFallback();
173				}
174			},
175			error: function(err) {
176				hideTyping();
177				appendFallback();
178			}
179		});
180	}
181
182	// Process response data from Bot API
183	function processBotResponse(data) {
184		const replyText = data.reply;
185		appendMessage(replyText, 'bot', data.choices || []);
186		
187		// If lead synchronization or phone collected was successful, save info
188		if (data.session_state) {
189			localStorage.setItem('spai_state', data.session_state);
190		}
191		
192		// Custom Redirect or WhatsApp CTA handling
193		if (data.whatsapp_url) {
194			appendWhatsAppButton(data.whatsapp_url);
195		}
196
197		scrollToBottom();
198	}
199
200	// Append Message UI Helper
201	function appendMessage(text, role, choices = []) {
202		const timeStr = getCurrentTime();
203		const roleClass = role === 'user' ? 'spai-message-user' : 'spai-message-bot';
204		
205		// Convert text formatting (newlines and simple bold/bullets)
206		let formattedText = escapeHtml(text)
207			.replace(/\n/g, '<br>')
208			.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
209			.replace(/^- (.*?)(?=<br>|$)/gm, '<li>$1</li>');
210
211		// Handle unordered lists wrapping
212		if (formattedText.includes('<li>')) {
213			formattedText = formattedText.replace(/(<li>.*?<\/li>)+/g, '<ul>$&</ul>');
214		}
215
216		let choiceHtml = '';
217		if (choices && choices.length > 0) {
218			choiceHtml = '<div class="spai-choice-buttons">';
219			choices.forEach(choice => {
220				if (choice.type === 'url') {
221					choiceHtml += `<a href="${escapeHtml(choice.url)}" target="_blank" class="spai-choice-btn" style="text-decoration:none; text-align:center; display:inline-block; line-height:1.4;">${escapeHtml(choice.label)}</a>`;
222				} else {
223					choiceHtml += `<button class="spai-choice-btn" data-value="${escapeHtml(choice.value)}" data-type="${escapeHtml(choice.type || '')}">${escapeHtml(choice.label)}</button>`;
224				}
225			});
226			choiceHtml += '</div>';
227		}
228
229		const html = `
230			<div class="spai-message ${roleClass}">
231				<div class="spai-message-bubble">
232					<p>${formattedText}</p>
233					${choiceHtml}
234				</div>
235				<div class="spai-message-time">${timeStr}</div>
236			</div>
237		`;
238		
239		$messagesList.append(html);
240	}
241
242	// Append WhatsApp Click-to-chat CTA button
243	function appendWhatsAppButton(url) {
244		const html = `
245			<div class="spai-message spai-message-bot">
246				<div class="spai-message-bubble" style="background-color: #25d366; color: #fff; text-align: center;">
247					<p><strong>Request Recorded!</strong></p>
248					<p>Click below to start chat immediately:</p>
249					<div class="spai-choice-buttons" style="margin-top: 5px;">
250						<a href="${url}" target="_blank" class="spai-choice-btn" style="background: #fff; color: #25d366; text-decoration: none; text-align: center; display: block; border-color: #fff; padding: 10px 15px; border-radius: 8px;">💬 Start Chat</a>
251					</div>
252				</div>
253				<div class="spai-message-time">${getCurrentTime()}</div>
254			</div>
255		`;
256		$messagesList.append(html);
257	}
258
259	// Fallback UI when API error occurs
260	function appendFallback() {
261		const whatsappNumber = spaiSettings.businessWhatsApp || '';
262		const phone = spaiSettings.businessPhone || '';
263		const company = spaiSettings.companyName || 'our team';
264		
265		let buttonsHtml = '';
266		if (phone) {
267			buttonsHtml += `<a href="tel:${phone}" class="spai-choice-btn" style="text-decoration: none; display: block; text-align: center;">📞 Call Us</a>`;
268		}
269		if (whatsappNumber) {
270			buttonsHtml += `<a href="https://wa.me/${whatsappNumber}" target="_blank" class="spai-choice-btn" style="text-decoration: none; display: block; text-align: center;">💬 WhatsApp Chat</a>`;
271		}
272
273		const html = `
274			<div class="spai-message spai-message-bot">
275				<div class="spai-message-bubble">
276					<p>Sorry, I'm unable to answer that right now. You can contact ${company} directly for assistance.</p>
277					${buttonsHtml ? `<div class="spai-choice-buttons">${buttonsHtml}</div>` : ''}
278				</div>
279				<div class="spai-message-time">${getCurrentTime()}</div>
280			</div>
281		`;
282		$messagesList.append(html);
283		scrollToBottom();
284	}
285
286	// Load chat transcripts to resume conversations
287	function loadChatHistory() {
288		$.ajax({
289			url: restUrl + 'chat?session_id=' + sessionId,
290			method: 'GET',
291			beforeSend: function(xhr) {
292				xhr.setRequestHeader('X-WP-Nonce', nonce);
293			},
294			success: function(response) {
295				if (response.success && response.data && response.data.length > 0) {
296					// Clear default greeting since history is loaded
297					$messagesList.empty();
298					response.data.forEach(msg => {
299						appendMessage(msg.message, msg.role);
300					});
301					scrollToBottom();
302				}
303			}
304		});
305	}
306
307	// Helper utilities
308	function scrollToBottom() {
309		$body.scrollTop($body[0].scrollHeight);
310	}
311
312	function showTyping() {
313		$typingIndicator.show();
314		scrollToBottom();
315	}
316
317	function hideTyping() {
318		$typingIndicator.hide();
319	}
320
321	function getCurrentTime() {
322		const now = new Date();
323		return now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
324	}
325
326	function escapeHtml(text) {
327		return text
328			.replace(/&/g, "&amp;")
329			.replace(/</g, "&lt;")
330			.replace(/>/g, "&gt;")
331			.replace(/"/g, "&quot;")
332			.replace(/'/g, "&#039;");
333	}
334
335	// Close on ESC key press
336	$(document).on('keyup', function(e) {
337		if (e.key === 'Escape') {
338			closeChat();
339		}
340	});
341});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.