PluginProbe
Post Snippets – Custom WordPress Code Snippets Customizer / 4.2.2
Post Snippets – Custom WordPress Code Snippets Customizer v4.2.2
4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 1.7.2 1.7.3 1.8 1.8.1 1.8.2 1.8.3 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.8.9.1 1.8.9.2 1.9 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 All 115 releases
post-snippets / assets / chatbot-script.js

chatbot-script.js in Post Snippets – Custom WordPress Code Snippets Customizer 4.2.2, at assets/chatbot-script.js

634 lines 22.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * Chatbot JS (Premium Sync)
3 */
4
5 jQuery(document).ready(function ($) {
6 const chatbotState = {
7 conversationMessages: [],
8 apiKeyValidated: false,
9 currentEditor: null,
10 editors: {
11 custom: null,
12 css: null,
13 js: null
14 }
15 };
16
17 const icons = {
18 ai: `<div class="msg-icon ai-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg></div>`,
19 user: `<div class="msg-icon user-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>`,
20 warning: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,
21 success: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,
22 book: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M6.5 2H20v20H6.5"/></svg>`
23 };
24
25
26
27 initializeChatbot();
28
29 function initializeChatbot() {
30 $('#chatbot-wrapper').hide();
31 bindEventHandlers();
32 initializeCodeMirrorDetection();
33 }
34
35 function bindEventHandlers() {
36 $('#chatbot-toggle').on('click', handleChatbotToggle);
37 $('#chatbot-close').on('click', () => $('#chatbot-wrapper').fadeOut('fast'));
38 $('#chatbot-send').on('click', handleSendMessage);
39 $('#chatbot-input').on('keypress', handleInputKeypress);
40 $(document).on('click', '#chatbot-input-container', () => $('#chatbot-input').focus());
41 $('#chatbot-input').on('input', handleInputResize);
42 $(document).on('click', '.apply-snippet-btn', handleApplySnippet);
43 $(document).on('click', '.read-code-btn', handleReadCode);
44 }
45
46 function handleChatbotToggle() {
47 const wrapper = $('#chatbot-wrapper');
48 if (wrapper.is(':visible')) {
49 wrapper.fadeOut('fast');
50 } else {
51 wrapper.css('display', 'flex').hide().fadeIn('fast');
52 if (!chatbotState.apiKeyValidated) {
53 checkApiKey();
54 }
55 }
56 }
57
58 function handleClearChat() {
59 if (confirm('Are you sure you want to clear this conversation?')) {
60 chatbotState.conversationMessages = [];
61 $('#chatbot-messages').empty();
62 showWelcomeMessage();
63 }
64 }
65
66 function handleSendMessage(e) {
67 e.preventDefault();
68 sendMessage();
69 }
70
71 function handleInputKeypress(e) {
72 if (e.which === 13 && !e.shiftKey) {
73 e.preventDefault();
74 sendMessage();
75 }
76 }
77
78 function handleInputResize() {
79 this.style.height = 'auto';
80 this.style.height = Math.min(this.scrollHeight, 120) + 'px';
81 }
82
83 function handleApplySnippet() {
84 const codeId = $(this).data('code-id');
85 const language = $(this).data('language');
86 const codeElement = $('#' + codeId);
87
88 if (codeElement.length) {
89 const rawCode = decodeURIComponent(codeElement.attr('data-raw') || '');
90 const code = rawCode || codeElement.text();
91
92 applyCodeToEditor(code, language);
93 showButtonFeedback($(this), 'Applied!');
94 }
95 }
96
97 function handleReadCode() {
98 const editorType = $(this).data('editor-type');
99 const code = readCodeFromEditor(editorType);
100
101 if (code) {
102 const messages = $('#chatbot-messages');
103 messages.append(createReadCodeMessage(editorType, code));
104 scrollToBottom();
105 }
106 }
107
108 function checkApiKey() {
109 $.ajax({
110 type: 'POST',
111 url: PSchatbot.ajaxurl,
112 data: {
113 action: 'check_api_key',
114 nonce: PSchatbot.nonce
115 },
116 success: function (response) {
117 if (response.success) {
118 chatbotState.apiKeyValidated = true;
119 showWelcomeMessage();
120 } else {
121 showApiKeyError();
122 }
123 },
124 error: function () {
125 showApiKeyError();
126 }
127 });
128 }
129
130 function showWelcomeMessage() {
131 const messages = $('#chatbot-messages');
132 if (messages.children().length === 0) {
133 messages.append(`
134 <div class="welcome-message">
135 <div class="message-content">
136 <strong>AI Assistant</strong> Hello! I'm your AI coding assistant. I can help you with PHP, JavaScript, CSS, and general programming questions. I can also read and analyze your current code. What would you like to work on today?
137 </div>
138 </div>
139 `);
140 }
141 }
142
143 function showApiKeyError() {
144 const messages = $('#chatbot-messages');
145 messages.html(`
146 <div class="error-message">
147 <div class="message-content">
148 <strong>Configuration Required</strong>
149 Please add your OpenAI API key in the
150 <a href="${PSchatbot.settings_url}" target="_blank" style="color: inherit; text-decoration: underline;">
151 AI Chatbot settings
152 </a>
153 before starting the chat.
154 </div>
155 </div>
156 `);
157 $('#chatbot-input').prop('disabled', true);
158 $('#chatbot-send').prop('disabled', true);
159 }
160
161 function sendMessage() {
162 if (!chatbotState.apiKeyValidated) {
163 checkApiKey();
164 return;
165 }
166
167 const input = $('#chatbot-input');
168 const userMessage = $.trim(input.val());
169
170 if (!userMessage) return;
171
172 const codeAnalysisKeywords = ['syntax error', 'fix', 'debug', 'error in my file', 'check my code', 'analyze'];
173 const needsCodeAnalysis = codeAnalysisKeywords.some(keyword =>
174 userMessage.toLowerCase().includes(keyword)
175 );
176
177 let messageToSend = userMessage;
178 let currentCode = '';
179
180 if (needsCodeAnalysis) {
181 const detectedEditor = detectActiveEditor();
182 if (detectedEditor) {
183 currentCode = readCodeFromEditor(detectedEditor);
184 if (currentCode) {
185 messageToSend = `${userMessage}\n\nCurrent code from ${detectedEditor} editor:\n\`\`\`${getLanguageFromEditor(detectedEditor)}\n${currentCode}\n\`\`\``;
186 }
187 }
188 }
189
190 displayUserMessage(userMessage);
191 input.val('');
192 scrollToBottom();
193
194 const loadingId = showLoadingMessage();
195 disableSendButton();
196
197 sendAjaxRequest(messageToSend, loadingId);
198 }
199
200 function detectActiveEditor() {
201 const editors = ['custom', 'css', 'js'];
202
203 for (const editorType of editors) {
204 const editor = getEditorByType(editorType);
205 if (editor && editor.hasFocus && editor.hasFocus()) {
206 return editorType;
207 }
208 }
209
210 for (const editorType of editors) {
211 const code = readCodeFromEditor(editorType);
212 if (code && code.trim().length > 0) {
213 return editorType;
214 }
215 }
216
217 return 'custom';
218 }
219
220 function getLanguageFromEditor(editorType) {
221 const languageMap = {
222 'custom': 'php',
223 'css': 'css',
224 'js': 'javascript'
225 };
226 return languageMap[editorType] || 'text';
227 }
228
229 function displayUserMessage(message) {
230 const messages = $('#chatbot-messages');
231 messages.append(`
232 <div class="user-message">
233 ${icons.user}
234 <div class="message-content">
235 <strong>You</strong> ${escapeHtml(message)}
236 </div>
237 </div>
238 `);
239 }
240
241 function showLoadingMessage() {
242 const loadingId = 'loading-' + Date.now();
243 const messages = $('#chatbot-messages');
244 messages.append(`
245 <div id="${loadingId}" class="ai-message loading">
246 ${icons.ai}
247 <div class="message-content">
248 <strong>AI Assistant</strong>
249 <span class="typing-indicator">
250 <span></span>
251 <span></span>
252 <span></span>
253 </span>
254 </div>
255 </div>
256 `);
257 scrollToBottom();
258 return loadingId;
259 }
260
261 function disableSendButton() {
262 $('#chatbot-send').prop('disabled', true);
263 }
264
265 function enableSendButton() {
266 $('#chatbot-send').prop('disabled', false);
267 $('#chatbot-input').focus();
268 }
269
270 function sendAjaxRequest(message, loadingId) {
271 $.ajax({
272 type: 'POST',
273 url: PSchatbot.ajaxurl,
274 data: {
275 action: 'chatbot_ask',
276 prompt: message,
277 messages: JSON.stringify(chatbotState.conversationMessages),
278 nonce: PSchatbot.nonce
279 },
280 timeout: 60000,
281 success: function (response) {
282 handleAjaxSuccess(response, loadingId);
283 },
284 error: function (xhr, status, error) {
285 handleAjaxError(xhr, status, error, loadingId);
286 },
287 complete: function () {
288 enableSendButton();
289 }
290 });
291 }
292
293 function handleAjaxSuccess(response, loadingId) {
294 $('#' + loadingId).remove();
295
296 if (response.success && response.data) {
297 const content = response.data.content || response.data;
298 const processedContent = processAIResponse(content);
299
300 displayAIMessage(processedContent);
301
302 if (response.data.messages) {
303 chatbotState.conversationMessages = response.data.messages;
304 }
305 } else {
306 const errorMsg = response.data ? response.data : 'Unknown error occurred';
307 showErrorMessage(errorMsg);
308 }
309
310 scrollToBottom();
311 }
312
313 function handleAjaxError(xhr, status, error, loadingId) {
314 $('#' + loadingId).remove();
315
316 let errorMessage;
317 if (status === 'timeout') {
318 errorMessage = 'Request timed out. Please try again with a shorter message.';
319 } else if (xhr.responseJSON && xhr.responseJSON.data) {
320 errorMessage = xhr.responseJSON.data;
321 } else if (xhr.responseText) {
322 try {
323 const errorData = JSON.parse(xhr.responseText);
324 errorMessage = errorData.data || 'Server error occurred';
325 } catch (e) {
326 errorMessage = 'Server error occurred. Please try again.';
327 }
328 } else {
329 errorMessage = 'Network error. Please check your connection and try again.';
330 }
331
332 showErrorMessage(errorMessage);
333 scrollToBottom();
334 }
335
336 function displayAIMessage(content) {
337 const messages = $('#chatbot-messages');
338 messages.append(`
339 <div class="ai-message">
340 ${icons.ai}
341 <div class="message-content">
342 <strong>AI Assistant</strong>
343 <div class="ai-response">${content}</div>
344 </div>
345 </div>
346 `);
347 }
348
349 function processAIResponse(content) {
350 content = content.replace(/```(\w+)?\n([\s\S]*?)```/g, function (match, language, code) {
351 const lang = language || 'text';
352 const escapedCode = escapeHtml(code.trim());
353 const rawCode = code.trim();
354 const uniqueId = 'code-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
355
356 return `<div class="code-block" data-language="${lang}">
357 <div class="code-header">
358 <span class="language-label">${lang.toUpperCase()}</span>
359 </div>
360 <pre><code class="language-${lang}" id="${uniqueId}" data-raw="${encodeURIComponent(rawCode)}">${escapedCode}</code></pre>
361 <div class="code-actions">
362 <button class="apply-snippet-btn" data-code-id="${uniqueId}" data-language="${lang}">Apply Snippet</button>
363 <button class="copy-code-btn" data-code-id="${uniqueId}">Copy Code</button>
364 </div>
365 </div>`;
366 });
367
368 content = content.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
369 content = content.replace(/\n/g, '<br>');
370
371 return content;
372 }
373
374 function readCodeFromEditor(editorType) {
375 const editor = getEditorByType(editorType);
376
377 if (editor) {
378 try {
379 return editor.getValue();
380 } catch (error) {
381 console.error('Error reading from CodeMirror:', error);
382 }
383 }
384
385 const textarea = getTextareaByType(editorType);
386 if (textarea.length) {
387 return textarea.val();
388 }
389
390 return '';
391 }
392
393 function getEditorByType(editorType) {
394 if (chatbotState.editors[editorType]) {
395 return chatbotState.editors[editorType];
396 }
397
398 const selectors = {
399 'custom': '.post-snippets-edit:not(.post-snippets-edit-css):not(.post-snippets-edit-js)',
400 'css': '.post-snippets-edit-css',
401 'js': '.post-snippets-edit-js'
402 };
403
404 const selector = selectors[editorType];
405 if (selector) {
406 const editorElement = $(selector).closest('.CodeMirror')[0];
407 if (editorElement && editorElement.CodeMirror) {
408 chatbotState.editors[editorType] = editorElement.CodeMirror;
409 return editorElement.CodeMirror;
410 }
411 }
412
413 return null;
414 }
415
416 function getTextareaByType(editorType) {
417 const textareaSelectors = {
418 'custom': 'textarea[name="snippet_code"], textarea#snippet_code',
419 'css': 'textarea[name="snippet_css"], textarea#snippet_css',
420 'js': 'textarea[name="snippet_js"], textarea#snippet_js'
421 };
422
423 const selector = textareaSelectors[editorType];
424 return selector ? $(selector) : $();
425 }
426
427 function createReadCodeMessage(editorType, code) {
428 const truncatedCode = code.length > 500 ? code.substring(0, 500) + '...' : code;
429 return `
430 <div class="system-message">
431 <div class="message-content">
432 <strong>Editor Snippet Read</strong>
433 <pre class="code-preview"><code>${escapeHtml(truncatedCode)}</code></pre>
434 <small>Total characters: ${code.length}</small>
435 </div>
436 </div>
437 `;
438 }
439
440 function applyCodeToEditor(code, language) {
441 const editorType = detectEditorTypeFromLanguage(language);
442 const editor = getEditorByType(editorType);
443
444 if (editor) {
445 try {
446 // If there's no selection, this will insert at the cursor.
447 // If there is a selection, it will replace it.
448 editor.replaceSelection(code);
449 editor.focus();
450 editor.refresh();
451
452 $(editor.getWrapperElement()).trigger('change');
453 const textarea = $(editor.getTextArea());
454 if (textarea.length) {
455 textarea.trigger('change');
456 }
457
458 showSuccessMessage(`Code applied to ${editorType} editor successfully!`);
459 return true;
460 } catch (error) {
461 console.error('Error applying code to CodeMirror:', error);
462 }
463 }
464
465 const textarea = getTextareaByType(editorType);
466 if (textarea.length) {
467 insertAtCaret(textarea, code);
468 showSuccessMessage(`Code applied to ${editorType} editor successfully!`);
469 return true;
470 }
471
472 copyToClipboard(code);
473 showSuccessMessage('Code copied to clipboard! Please paste it into your editor.');
474 return false;
475 }
476
477 function insertAtCaret(textarea, text) {
478 const el = textarea[0];
479 const scrollPos = el.scrollTop;
480 const strPos = el.selectionStart;
481 const front = (el.value).substring(0, strPos);
482 const back = (el.value).substring(el.selectionEnd, el.value.length);
483
484 el.value = front + text + back;
485 textarea.trigger('change').focus();
486 el.selectionStart = strPos + text.length;
487 el.selectionEnd = strPos + text.length;
488 el.scrollTop = scrollPos;
489 }
490
491 function detectEditorTypeFromLanguage(language) {
492 const languageMap = {
493 'php': 'custom',
494 'html': 'custom',
495 'css': 'css',
496 'javascript': 'js',
497 'js': 'js'
498 };
499 return languageMap[language] || 'custom';
500 }
501
502 function showButtonFeedback(button, text) {
503 const originalText = button.text();
504 button.text(text).addClass('applied');
505
506 setTimeout(() => {
507 button.text(originalText).removeClass('applied');
508 }, 2000);
509 }
510
511 function copyToClipboard(text) {
512 const tempTextarea = document.createElement('textarea');
513 tempTextarea.value = text;
514 document.body.appendChild(tempTextarea);
515 tempTextarea.select();
516 tempTextarea.setSelectionRange(0, 99999);
517
518 try {
519 document.execCommand('copy');
520 } catch (err) {
521 console.error('Failed to copy text:', err);
522 }
523
524 document.body.removeChild(tempTextarea);
525 }
526
527 function showSuccessMessage(message) {
528 const messages = $('#chatbot-messages');
529 const successDiv = $(`
530 <div class="success-message">
531 <div class="message-content">
532 <strong>Success</strong> ${escapeHtml(message)}
533 </div>
534 </div>
535 `);
536
537 messages.append(successDiv);
538 scrollToBottom();
539
540 setTimeout(() => {
541 successDiv.fadeOut(() => successDiv.remove());
542 }, 3000);
543 }
544
545 function showErrorMessage(message) {
546 const messages = $('#chatbot-messages');
547 messages.append(`
548 <div class="error-message">
549 <div class="message-content">
550 <strong>Error</strong> ${escapeHtml(message)}
551 </div>
552 </div>
553 `);
554 }
555
556 function scrollToBottom() {
557 const messages = $('#chatbot-messages');
558 messages.scrollTop(messages[0].scrollHeight);
559 }
560
561 function escapeHtml(text) {
562 const div = document.createElement('div');
563 div.textContent = text;
564 return div.innerHTML;
565 }
566
567 function initializeCodeMirrorDetection() {
568 setTimeout(() => {
569 detectAllEditors();
570 }, 1000);
571
572 const observer = new MutationObserver((mutations) => {
573 mutations.forEach((mutation) => {
574 if (mutation.type === 'childList') {
575 mutation.addedNodes.forEach((node) => {
576 if (node.nodeType === 1 && node.classList && node.classList.contains('CodeMirror')) {
577 if (node.CodeMirror) {
578 cacheEditorInstance(node.CodeMirror);
579 }
580 }
581 });
582 }
583 });
584 });
585
586 observer.observe(document.body, {
587 childList: true,
588 subtree: true
589 });
590
591 setTimeout(() => observer.disconnect(), 10000);
592 }
593
594 function detectAllEditors() {
595 const editorSelectors = {
596 'custom': '.post-snippets-edit:not(.post-snippets-edit-css):not(.post-snippets-edit-js)',
597 'css': '.post-snippets-edit-css',
598 'js': '.post-snippets-edit-js'
599 };
600
601 Object.keys(editorSelectors).forEach(editorType => {
602 const selector = editorSelectors[editorType];
603 $(selector).each(function () {
604 const codeMirrorElement = $(this).closest('.CodeMirror')[0];
605 if (codeMirrorElement && codeMirrorElement.CodeMirror) {
606 chatbotState.editors[editorType] = codeMirrorElement.CodeMirror;
607 }
608 });
609 });
610 }
611
612 function cacheEditorInstance(editor) {
613 const wrapper = $(editor.getWrapperElement());
614
615 if (wrapper.find('.post-snippets-edit-css').length) {
616 chatbotState.editors.css = editor;
617 } else if (wrapper.find('.post-snippets-edit-js').length) {
618 chatbotState.editors.js = editor;
619 } else {
620 chatbotState.editors.custom = editor;
621 }
622 }
623
624 $(document).on('click', '.copy-code-btn', function () {
625 const codeId = $(this).data('code-id');
626 const codeElement = $('#' + codeId);
627
628 if (codeElement.length) {
629 const code = codeElement.text();
630 copyToClipboard(code);
631 showButtonFeedback($(this), 'Copied!');
632 }
633 });
634 });