1jQuery(function ($) { 2 function escapeHtml(text) { 3 return $('<div>').text(text).html(); 4 } 5 6 function linkify(text) { 7 var escaped = escapeHtml(text); 8 9 return escaped.replace( 10 /((https?:\/\/|www\.)[^\s<]+)/gi, 11 function (url) { 12 var href = url; 13 14 if (!/^https?:\/\//i.test(href)) { 15 href = 'https://' + href; 16 } 17 18 return '<a href="' + href + '" target="_blank" rel="noopener noreferrer">' + url + '</a>'; 19 } 20 ); 21 } 22 23 function nl2br(text) { 24 return text.replace(/\n/g, '<br>'); 25 } 26 27 function appendMessage(role, text) { 28 var cssClass = role === 'user' ? 'user' : 'assistant'; 29 var html = ''; 30 31 if (role === 'assistant') { 32 html = nl2br(linkify(text)); 33 } else { 34 html = nl2br(escapeHtml(text)); 35 } 36 37 $('#lexicon-ai-messages').append( 38 '<div class="lexicon-ai-message ' + cssClass + '">' + html + '</div>' 39 ); 40 41 scrollMessagesToBottom(); 42 } 43 44 function appendThinkingMessage() { 45 removeThinkingMessage(); 46 47 $('#lexicon-ai-messages').append( 48 '<div class="lexicon-ai-message assistant lexicon-ai-thinking" id="lexicon-ai-thinking">' + 49 '<span class="lexicon-ai-thinking-label">AI is thinking</span>' + 50 '<span class="lexicon-ai-thinking-dots">' + 51 '<span></span><span></span><span></span>' + 52 '</span>' + 53 '</div>' 54 ); 55 56 scrollMessagesToBottom(); 57 } 58 59 function removeThinkingMessage() { 60 $('#lexicon-ai-thinking').remove(); 61 } 62 63 function scrollMessagesToBottom() { 64 var messagesBox = document.getElementById('lexicon-ai-messages'); 65 if (messagesBox) { 66 messagesBox.scrollTop = messagesBox.scrollHeight; 67 } 68 } 69 70 function setLoading(isLoading) { 71 $('#lexicon-ai-send').prop('disabled', isLoading); 72 $('#lexicon-ai-input').prop('disabled', isLoading); 73 } 74 75 function openChat() { 76 $('#lexicon-ai-chat-box').removeClass('lexicon-ai-hidden'); 77 $('#lexicon-ai-chat-toggle').addClass('lexicon-ai-hidden'); 78 scrollMessagesToBottom(); 79 setTimeout(function () { 80 $('#lexicon-ai-input').focus(); 81 }, 50); 82 } 83 84 function closeChat() { 85 $('#lexicon-ai-chat-box').addClass('lexicon-ai-hidden'); 86 $('#lexicon-ai-chat-toggle').removeClass('lexicon-ai-hidden'); 87 } 88 89 function sendMessage() { 90 var message = $('#lexicon-ai-input').val().trim(); 91 92 if (!message) { 93 return; 94 } 95 96 openChat(); 97 appendMessage('user', message); 98 $('#lexicon-ai-input').val(''); 99 setLoading(true); 100 appendThinkingMessage(); 101 102 $.ajax({ 103 url: LexiconAIChat.ajaxurl, 104 type: 'POST', 105 dataType: 'json', 106 data: { 107 action: 'lexicon_ai_chat', 108 nonce: LexiconAIChat.nonce, 109 message: message, 110 page_url: LexiconAIChat.pageUrl || '' 111 } 112 }) 113 .done(function (response) { 114 removeThinkingMessage(); 115 116 if (response && response.success && response.data && response.data.answer) { 117 appendMessage('assistant', response.data.answer); 118 } else if (response && response.data && response.data.message) { 119 appendMessage('assistant', 'Error: ' + response.data.message); 120 } else { 121 appendMessage('assistant', 'An unknown error occurred.'); 122 } 123 }) 124 .fail(function () { 125 removeThinkingMessage(); 126 appendMessage('assistant', 'Request failed. Please try again.'); 127 }) 128 .always(function () { 129 setLoading(false); 130 $('#lexicon-ai-input').focus(); 131 }); 132 } 133 134 $(document).on('click', '#lexicon-ai-chat-toggle', function () { 135 openChat(); 136 }); 137 138 $(document).on('click', '#lexicon-ai-chat-close', function () { 139 closeChat(); 140 }); 141 142 $(document).on('click', '#lexicon-ai-send', function () { 143 sendMessage(); 144 }); 145 146 $(document).on('keydown', '#lexicon-ai-input', function (e) { 147 if (e.key === 'Enter' && !e.shiftKey) { 148 e.preventDefault(); 149 sendMessage(); 150 } 151 }); 152});
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.