PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 All 140 releases
← All changes | assets/src/js/admin/generate-with-openai.js +638 -642 4.3.9 → 4.4.9 View file →
@@ -1,642 +1,638 @@
1 -/**
2 - * Generate data with OpenAI
3 - */
4 -
5 -import * as lpUtils from './../utils.js';
6 -import SweetAlert from 'sweetalert2';
7 -import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
8 -
9 -let lp_structure_course;
10 -let popupSweetAlert = null;
11 -let lp_is_generating_course_data = false;
12 -const lp_course_ai_setting = JSON.parse( localStorage.getItem( 'lp_course_ai_setting' ) ) || {};
13 -let isLayoutGutenberg = false;
14 -
15 -let selectGutenberg;
16 -let dispatchGutenberg;
17 -let editorGutenberg;
18 -
19 -export class GenerateWithOpenai {
20 - constructor( options = {} ) {
21 - this.options = {
22 - autoInsertButtons: true,
23 - isCourseBuilder: false,
24 - titleInputSelector: 'input[name=post_title]',
25 - courseBuilderTitleInputSelector: 'input[name=course_title]',
26 - editorIdClassic: 'content',
27 - editorIdCourseBuilder: 'course_description_editor',
28 - ...options,
29 - };
30 -
31 - this.init();
32 - }
33 -
34 - static selectors = {
35 - elBtnGenerateWithAi: '.lp-btn-generate-with-ai',
36 - elGenerateDataAiWrap: '.lp-generate-data-ai-wrap',
37 - };
38 -
39 - init() {
40 - if ( ! lpData?.enable_open_ai ) {
41 - return;
42 - }
43 -
44 - if ( this.options.autoInsertButtons ) {
45 - lpUtils.lpOnElementReady( '#titlewrap', ( el ) => {
46 - el.insertAdjacentHTML(
47 - 'afterend',
48 - `<button type="button"
49 - style="margin-left: 5px"
50 - class="lp-btn-generate-with-ai lp-btn-ai-style"
51 - data-template="#lp-tmpl-edit-title-ai">
52 - <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
53 - </button>`
54 - );
55 - } );
56 - lpUtils.lpOnElementReady( '#wp-content-media-buttons', ( el ) => {
57 - el.insertAdjacentHTML(
58 - 'beforeend',
59 - `<button type="button"
60 - style="padding: 5px 10px; justify-content: center;"
61 - class="lp-btn-generate-with-ai lp-btn-ai-style"
62 - data-template="#lp-tmpl-edit-description-ai">
63 - <i class="lp-ico-ai"></i><span>Generate description with AI</span>
64 - </button>`,
65 - );
66 - } );
67 - lpUtils.lpOnElementReady( '#postimagediv', ( el ) => {
68 - const elInside = el.querySelector( '.postbox-header' );
69 - elInside.insertAdjacentHTML(
70 - 'afterend',
71 - `<button type="button"
72 - style="padding: 5px 10px; justify-content: center;"
73 - class="lp-btn-generate-with-ai lp-btn-ai-style"
74 - data-template="#lp-tmpl-edit-image-ai">
75 - <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
76 - </button>`
77 - );
78 - } );
79 -
80 - // Check is layout Gutenberg
81 - if ( wp.data && wp.data.select( 'core/editor' ) ) {
82 - isLayoutGutenberg = true;
83 - selectGutenberg = wp.data.select;
84 - dispatchGutenberg = wp.data.dispatch;
85 - editorGutenberg = selectGutenberg( 'core/editor' );
86 -
87 - // For layout Gutenberg - button for title
88 - lpUtils.lpOnElementReady( '.editor-document-bar', ( el ) => {
89 - el.insertAdjacentHTML(
90 - 'afterend',
91 - `<button type="button"
92 - style="margin-left: 5px"
93 - class="lp-btn-generate-with-ai"
94 - data-template="#lp-tmpl-edit-title-ai">
95 - <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
96 - </button>`
97 - );
98 - } );
99 -
100 - // For layout Gutenberg - button for description
101 - lpUtils.lpOnElementReady( '.editor-post-featured-image', ( el ) => {
102 - el.insertAdjacentHTML(
103 - 'beforebegin',
104 - `<button type="button"
105 - style="padding: 5px 10px; justify-content: center;"
106 - class="lp-btn-generate-with-ai"
107 - data-template="#lp-tmpl-edit-description-ai">
108 - <i class="lp-ico-ai"></i><span>Generate description with AI</span>
109 - </button>`
110 - );
111 - } );
112 -
113 - // For layout Gutenberg - button for image
114 - lpUtils.lpOnElementReady( '.editor-post-featured-image', ( el ) => {
115 - el.insertAdjacentHTML(
116 - 'afterend',
117 - `<button type="button"
118 - style="padding: 5px 10px; justify-content: center;"
119 - class="lp-btn-generate-with-ai"
120 - data-template="#lp-tmpl-edit-image-ai">
121 - <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
122 - </button>`
123 - );
124 - } );
125 - }
126 - }
127 -
128 - this.events();
129 - }
130 -
131 - getTitleInputSelector() {
132 - return this.options.isCourseBuilder
133 - ? this.options.courseBuilderTitleInputSelector
134 - : this.options.titleInputSelector;
135 - }
136 -
137 - getEditorId() {
138 - return this.options.isCourseBuilder
139 - ? this.options.editorIdCourseBuilder
140 - : this.options.editorIdClassic;
141 - }
142 -
143 - setCourseBuilderPostId( form ) {
144 - if ( ! this.options.isCourseBuilder || ! form ) {
145 - return;
146 - }
147 -
148 - const postIdInput = form.querySelector( '[name="post-id"]' );
149 - if ( ! postIdInput ) {
150 - return;
151 - }
152 -
153 - const courseEditWrap = document.querySelector( '.cb-section__course-edit[data-course-id]' );
154 - const courseId = courseEditWrap?.dataset?.courseId || '';
155 - if ( courseId ) {
156 - postIdInput.value = courseId;
157 - }
158 - }
159 -
160 - extractImageSourceFromHtml( html ) {
161 - if ( ! html ) {
162 - return '';
163 - }
164 -
165 - const tempWrapper = document.createElement( 'div' );
166 - tempWrapper.innerHTML = html;
167 - return tempWrapper.querySelector( 'img' )?.getAttribute( 'src' ) || '';
168 - }
169 -
170 - applyCourseBuilderFeaturedImage( attachmentId, imageSrc ) {
171 - if ( ! attachmentId || ! imageSrc ) {
172 - return false;
173 - }
174 -
175 - document.dispatchEvent(
176 - new CustomEvent( 'lp-course-builder/ai-featured-image-applied', {
177 - detail: {
178 - attachmentId,
179 - imageSrc,
180 - },
181 - } )
182 - );
183 -
184 - return true;
185 - }
186 -
187 - events() {
188 - lpUtils.eventHandlers( 'click', [
189 - {
190 - selector: GenerateWithOpenai.selectors.elBtnGenerateWithAi,
191 - class: this,
192 - callBack: this.showPopup.name,
193 - },
194 - {
195 - selector: '.lp-btn-step',
196 - class: this,
197 - callBack: this.showStep.name,
198 - },
199 - {
200 - selector: '.lp-btn-generate-prompt',
201 - class: this,
202 - callBack: this.generatePrompt.name,
203 - },
204 - {
205 - selector: '.lp-btn-call-open-ai',
206 - class: this,
207 - callBack: this.generateData.name,
208 - },
209 - {
210 - selector: '.lp-btn-copy',
211 - class: this,
212 - callBack: this.copyGeneratedData.name,
213 - },
214 - {
215 - selector: '.lp-btn-apply',
216 - class: this,
217 - callBack: this.applyGeneratedData.name,
218 - },
219 - {
220 - selector: '.lp-btn-ai-apply-image',
221 - class: this,
222 - callBack: this.applyImageData.name,
223 - },
224 - {
225 - selector: '.lp-btn-close-ai-popup',
226 - //class: this,
227 - callBack: ( args ) => {
228 - const { e, target } = args;
229 -
230 - const message = lpData.i18n.confirm_close_ai;
231 -
232 - if ( ! lp_is_generating_course_data ) {
233 - SweetAlert.close();
234 - } else if ( confirm( message ) ) {
235 - SweetAlert.close();
236 - }
237 -
238 - // Testing custom confirm box
239 - /*if ( confirm( message ) ) {
240 - SweetAlert.close();
241 - }*/
242 - },
243 - },
244 - ] );
245 - }
246 -
247 - showPopup( args ) {
248 - const { e, target } = args;
249 - const templateId =
250 - target.dataset.template ||
251 - target.closest( '.lp-btn-generate-with-ai' ).dataset.template ||
252 - target.closest( '.lp-generate-data-ai-wrap' ).dataset.template ||
253 - '';
254 -
255 - const modalTemplate = document.querySelector( templateId );
256 -
257 - if ( ! modalTemplate ) {
258 - console.error( `Template ${ templateId } not found!` );
259 - return;
260 - }
261 -
262 - SweetAlert.fire( {
263 - html: modalTemplate.innerHTML,
264 - width: '60%',
265 - showCloseButton: false,
266 - showConfirmButton: false,
267 - allowOutsideClick: false,
268 - allowEscapeKey: false,
269 - didOpen: () => {
270 - popupSweetAlert = SweetAlert.getPopup();
271 - // Click to show tomSelect style
272 - popupSweetAlert.click();
273 -
274 - // Set post title and post content to hidden fields of form to AI prompt reference
275 - const elPostTitleInput = document.querySelector( this.getTitleInputSelector() );
276 - let post_title = '';
277 - if ( elPostTitleInput ) {
278 - post_title = elPostTitleInput.value;
279 - } else if ( isLayoutGutenberg ) {
280 - const elGutenbergTitle = document.querySelector( '.editor-post-card-panel__title-name' );
281 - if ( elGutenbergTitle ) {
282 - post_title = elGutenbergTitle.textContent;
283 - }
284 - }
285 -
286 - let post_content = '';
287 - if ( ! isLayoutGutenberg ) {
288 - const editorId = this.getEditorId();
289 - if ( ! window.tinymce || ! window.tinymce.get( editorId ) ) {
290 - const editorElement = document.querySelector( `#${ editorId }` );
291 - post_content = editorElement ? editorElement.value : '';
292 - } else {
293 - post_content = window.tinymce.get( editorId ).getContent( { format: 'text' } );
294 - }
295 - } else {
296 - const content = editorGutenberg.getEditedPostContent();
297 - post_content = content.replace( /(<([^>]+)>)/gi, '' ); // Remove HTML tags
298 - }
299 -
300 - const form = popupSweetAlert.querySelector( 'form' );
301 - this.setCourseBuilderPostId( form );
302 - const elPostTitle = form.querySelector( '[name=post-title]' );
303 - if ( elPostTitle ) {
304 - elPostTitle.value = post_title;
305 -
306 - if ( ! post_title ) {
307 - const elGroup = elPostTitle.closest( '.form-group' );
308 - const elReferWarning = elGroup.querySelector( '.lp-ai-warning-refer' );
309 - if ( elReferWarning ) {
310 - lpUtils.lpShowHideEl( elReferWarning, 1 );
311 - }
312 - }
313 - }
314 -
315 - const elPostContent = form.querySelector( '[name=post-content]' );
316 - if ( elPostContent ) {
317 - elPostContent.value = post_content;
318 -
319 - if ( post_content.length < 2 ) {
320 - const elGroup = elPostContent.closest( '.form-group' );
321 - const elReferWarning = elGroup.querySelector( '.lp-ai-warning-refer' );
322 - if ( elReferWarning ) {
323 - lpUtils.lpShowHideEl( elReferWarning, 1 );
324 - }
325 - }
326 - }
327 -
328 - const targetAudience = popupSweetAlert.querySelector( 'select[name="target_audience"]' );
329 - if ( targetAudience ) {
330 - if ( lp_course_ai_setting?.target_audience ) {
331 - targetAudience.tomselect.setValue( lp_course_ai_setting.target_audience );
332 - }
333 -
334 - targetAudience.addEventListener( 'change', ( event ) => {
335 - lp_course_ai_setting.target_audience = targetAudience.tomselect.getValue();
336 - localStorage.setItem( 'lp_course_ai_setting', JSON.stringify( lp_course_ai_setting ) );
337 - } );
338 - }
339 -
340 - const tone = popupSweetAlert.querySelector( 'select[name="tone"]' );
341 - if ( tone ) {
342 - if ( lp_course_ai_setting?.tone ) {
343 - tone.tomselect.setValue( lp_course_ai_setting.tone );
344 - }
345 -
346 - tone.addEventListener( 'change', ( event ) => {
347 - lp_course_ai_setting.tone = tone.tomselect.getValue();
348 - localStorage.setItem( 'lp_course_ai_setting', JSON.stringify( lp_course_ai_setting ) );
349 - } );
350 - }
351 -
352 - const language = popupSweetAlert.querySelector( 'select[name="language"]' );
353 - if ( language ) {
354 - if ( lp_course_ai_setting?.language ) {
355 - language.tomselect.setValue( lp_course_ai_setting.language );
356 - }
357 -
358 - language.addEventListener( 'change', ( event ) => {
359 - const value = language.tomselect.getValue();
360 - lp_course_ai_setting.language = value ? [ value ] : [];
361 - localStorage.setItem( 'lp_course_ai_setting', JSON.stringify( lp_course_ai_setting ) );
362 - } );
363 - }
364 - },
365 - } ).then( ( result ) => {
366 - if ( result.isDismissed ) {
367 - if ( lp_is_generating_course_data ) {
368 - lp_is_generating_course_data = false;
369 - }
370 - }
371 - } );
372 - }
373 -
374 - showStep( args ) {
375 - const { e, target } = args;
376 - e.preventDefault();
377 -
378 - const elBtnActions = target.closest( '.button-actions' );
379 - const elCreateCourseAIWrap = elBtnActions.closest(
380 - GenerateWithOpenai.selectors.elGenerateDataAiWrap
381 - );
382 - let step = parseInt( elBtnActions.dataset.step );
383 -
384 - const stepAction = target.dataset.action;
385 - if ( stepAction === 'next' ) {
386 - step++;
387 - } else if ( stepAction === 'prev' ) {
388 - step--;
389 - }
390 -
391 - elBtnActions.dataset.step = step;
392 - const elForm = target.closest( 'form' );
393 - const elContentStep = elForm.querySelector( `.step-content[data-step="${ step }"]` );
394 - const elItemStep = elCreateCourseAIWrap.querySelector( `.step-item[data-step="${ step }"]` );
395 - elForm.querySelectorAll( '.step-content' ).forEach( ( el ) => el.classList.remove( 'active' ) );
396 - elContentStep.classList.add( 'active' );
397 - elCreateCourseAIWrap
398 - .querySelectorAll( '.step-item' )
399 - .forEach( ( el ) => el.classList.remove( 'active' ) );
400 - elItemStep.classList.add( 'active' );
401 -
402 - // Get all buttons step to show/hide
403 - const form = target.closest( 'form' );
404 - const elBtnSteps = form.querySelectorAll( 'button[data-step-show]' );
405 - elBtnSteps.forEach( ( el ) => {
406 - const stepsShow = el.dataset.stepShow.split( ',' ).map( ( s ) => parseInt( s.trim() ) );
407 - if ( stepsShow.includes( step ) ) {
408 - lpUtils.lpShowHideEl( el, 1 );
409 - } else {
410 - lpUtils.lpShowHideEl( el, 0 );
411 - }
412 - } );
413 - }
414 -
415 - /**
416 - * Create prompt from data config
417 - * @param args
418 - */
419 - generatePrompt( args ) {
420 - const { e, target } = args;
421 - e.preventDefault();
422 - lpUtils.lpSetLoadingEl( target, true );
423 -
424 - // Get dataSend
425 - const form = target.closest( 'form' );
426 - let dataSend = JSON.parse( target.dataset.send );
427 - dataSend = lpUtils.mergeDataWithDatForm( form, dataSend );
428 -
429 - // Ajax to generate prompt
430 - const callBack = {
431 - success: ( response ) => {
432 - const { message, status, data } = response;
433 -
434 - lpToastify.show( message, status );
435 -
436 - if ( status === 'success' ) {
437 - const elPromptTextarea = form.querySelector(
438 - 'textarea[name=lp-openai-prompt-generated-field]'
439 - );
440 - elPromptTextarea.value = data;
441 - const elBtnNext = form.querySelector( '.lp-btn-step[data-action=next]' );
442 - elBtnNext.click();
443 - }
444 - },
445 - error: ( error ) => {
446 - lpToastify.show( error, 'error' );
447 - },
448 - completed: () => {
449 - lpUtils.lpSetLoadingEl( target, false );
450 - },
451 - };
452 -
453 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
454 - }
455 -
456 - /**
457 - * Submit prompt to OpenAI to generate course data
458 - * @param args
459 - */
460 - generateData( args ) {
461 - const { e, target } = args;
462 - e.preventDefault();
463 - lpUtils.lpSetLoadingEl( target, true );
464 -
465 - // Get dataSend
466 - const form = target.closest( 'form' );
467 - let dataSend = JSON.parse( target.dataset.send );
468 - dataSend = lpUtils.mergeDataWithDatForm( form, dataSend );
469 -
470 - const btnPrev = form.querySelector( '.lp-btn-step[data-action=prev]' );
471 - lpUtils.lpShowHideEl( btnPrev, 0 );
472 -
473 - // Ajax to generate prompt
474 - const callBack = {
475 - success: ( response ) => {
476 - const { message, status, data } = response;
477 - if ( lp_is_generating_course_data ) {
478 - lpToastify.show( message, status );
479 - }
480 -
481 - if ( status === 'success' ) {
482 - // Save structure data
483 - lp_structure_course = data.lp_structure_course;
484 -
485 - // Set preview HTML
486 - const elResults = form.querySelector( '.lp-ai-generated-results' );
487 - elResults.innerHTML = data.lp_html_preview;
488 -
489 - const elBtnNext = form.querySelector( '.lp-btn-step[data-action=next]' );
490 - elBtnNext.click();
491 - }
492 - },
493 - error: ( error ) => {
494 - lpToastify.show( error, 'error' );
495 - },
496 - completed: () => {
497 - lpUtils.lpSetLoadingEl( target, false );
498 - lpUtils.lpShowHideEl( btnPrev, 1 );
499 - lp_is_generating_course_data = false;
500 - },
501 - };
502 -
503 - lp_is_generating_course_data = true;
504 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
505 - }
506 -
507 - applyGeneratedData( args ) {
508 - const { e, target } = args;
509 - e.preventDefault();
510 -
511 - const dataApply = target.dataset.apply;
512 - const dataTarget = target.dataset.target;
513 -
514 - if ( dataTarget ) {
515 - if ( ! isLayoutGutenberg ) {
516 - if ( dataTarget === 'set-wp-editor-content' ) {
517 - this.setWPEditorContent( dataApply );
518 - } else if ( dataTarget === 'set-wp-title' ) {
519 - const elTitleInput = document.querySelector( this.getTitleInputSelector() );
520 - if ( elTitleInput ) {
521 - elTitleInput.value = dataApply;
522 - elTitleInput.dispatchEvent( new Event( 'input', { bubbles: true } ) );
523 - }
524 - }
525 - } else if ( dataTarget === 'set-wp-editor-content' ) {
526 - dispatchGutenberg( 'core/editor' ).editPost( { content: dataApply } );
527 - } else if ( dataTarget === 'set-wp-title' ) {
528 - dispatchGutenberg( 'core/editor' ).editPost( { title: dataApply } );
529 - }
530 - }
531 -
532 - if ( popupSweetAlert ) {
533 - SweetAlert.close();
534 - }
535 - }
536 -
537 - copyGeneratedData( args ) {
538 - const { e, target } = args;
539 - e.preventDefault();
540 -
541 - const dataCopy = target.dataset.copy;
542 -
543 - if ( navigator.clipboard ) {
544 - navigator.clipboard
545 - .writeText( dataCopy )
546 - .then( () => {
547 - lpToastify.show( 'Copied to clipboard!', 'success' );
548 - } )
549 - .catch( ( err ) => {
550 - lpToastify.show( 'Failed to copy text: ' + err, 'error' );
551 - } );
552 - } else {
553 - // Fallback when clipboard API is unavailable
554 - const textarea = document.createElement( 'textarea' );
555 - textarea.value = dataCopy;
556 - textarea.style.position = 'fixed';
557 - textarea.style.left = '-9999px';
558 - document.body.appendChild( textarea );
559 - textarea.select();
560 - try {
561 - const successful = document.execCommand( 'copy' );
562 - lpToastify.show(
563 - successful ? 'Copied to clipboard!' : 'Failed to copy text',
564 - successful ? 'success' : 'error'
565 - );
566 - } catch ( err ) {
567 - lpToastify.show( 'Failed to copy text: ' + err, 'error' );
568 - }
569 - document.body.removeChild( textarea );
570 - }
571 -
572 - /*navigator.clipboard.writeText( dataCopy ).then( () => {
573 - lpToastify.show( 'Copied to clipboard!', 'success' );
574 - } ).catch( ( err ) => {
575 - lpToastify.show( 'Failed to copy text: ' + err, 'error' );
576 - } );*/
577 - }
578 -
579 - setWPEditorContent( htmlContent ) {
580 - const editorId = this.getEditorId();
581 - const editor = window.tinymce?.get( editorId );
582 -
583 - if ( editor ) {
584 - editor.setContent( htmlContent );
585 - return;
586 - }
587 -
588 - const editorElement = document.querySelector( `#${ editorId }` );
589 - if ( editorElement ) {
590 - editorElement.value = htmlContent;
591 - editorElement.dispatchEvent( new Event( 'input', { bubbles: true } ) );
592 - }
593 - }
594 -
595 - applyImageData( args ) {
596 - const { e, target } = args;
597 - let dataSend = JSON.parse( target.dataset.send );
598 - dataSend = lpUtils.mergeDataWithDatForm( target.closest( 'form' ), dataSend );
599 - //e.preventDefault();
600 - lpUtils.lpSetLoadingEl( target, true );
601 -
602 - // Ajax to generate prompt
603 - const callBack = {
604 - success: ( response ) => {
605 - const { message, status, data } = response;
606 -
607 - lpToastify.show( message, status );
608 -
609 - if ( status === 'success' ) {
610 - if ( this.options.isCourseBuilder ) {
611 - const attachmentId = parseInt( data?.attachment_id || 0, 10 );
612 - const imageSrc = this.extractImageSourceFromHtml( data?.html_image || '' );
613 - const applied = this.applyCourseBuilderFeaturedImage( attachmentId, imageSrc );
614 -
615 - if ( ! applied ) {
616 - lpToastify.show( 'Failed to apply image to course featured image.', 'error' );
617 - return;
618 - }
619 - } else if ( ! isLayoutGutenberg ) {
620 - // Set image
621 - const elImagePreview = document.querySelector( '#postimagediv .inside' );
622 - elImagePreview.outerHTML = data.html_image;
623 - } else {
624 - dispatchGutenberg( 'core/editor' ).editPost( { featured_media: data.attachment_id } );
625 - }
626 -
627 - if ( popupSweetAlert ) {
628 - SweetAlert.close();
629 - }
630 - }
631 - },
632 - error: ( error ) => {
633 - lpToastify.show( error, 'error' );
634 - },
635 - completed: () => {
636 - lpUtils.lpSetLoadingEl( target, false );
637 - },
638 - };
639 -
640 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
641 - }
642 -}
1 +/**
2 + * Generate data with OpenAI
3 + */
4 +
5 +import * as lpUtils from './../utils.js';
6 +import SweetAlert from 'sweetalert2';
7 +import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
8 +
9 +let lp_structure_course;
10 +let popupSweetAlert = null;
11 +let lp_is_generating_course_data = false;
12 +const lp_course_ai_setting = JSON.parse( localStorage.getItem( 'lp_course_ai_setting' ) ) || {};
13 +let isLayoutGutenberg = false;
14 +
15 +let selectGutenberg;
16 +let dispatchGutenberg;
17 +let editorGutenberg;
18 +
19 +export class GenerateWithOpenai {
20 + constructor( options = {} ) {
21 + this.options = {
22 + autoInsertButtons: true,
23 + isCourseBuilder: false,
24 + titleInputSelector: 'input[name=post_title]',
25 + courseBuilderTitleInputSelector: 'input[name=course_title]',
26 + editorIdClassic: 'content',
27 + editorIdCourseBuilder: 'course_description_editor',
28 + ...options,
29 + };
30 +
31 + this.init();
32 + }
33 +
34 + static selectors = {
35 + elBtnGenerateWithAi: '.lp-btn-generate-with-ai',
36 + elGenerateDataAiWrap: '.lp-generate-data-ai-wrap',
37 + };
38 +
39 + init() {
40 + if ( ! lpData?.enable_open_ai ) {
41 + return;
42 + }
43 +
44 + if ( this.options.autoInsertButtons ) {
45 + lpUtils.lpOnElementReady( '#titlewrap', ( el ) => {
46 + el.insertAdjacentHTML(
47 + 'afterend',
48 + `<button type="button"
49 + style="margin-left: 5px"
50 + class="lp-btn-generate-with-ai lp-btn-ai-style"
51 + data-template="#lp-tmpl-edit-title-ai">
52 + <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
53 + </button>`
54 + );
55 + } );
56 + lpUtils.lpOnElementReady( '#wp-content-media-buttons', ( el ) => {
57 + el.insertAdjacentHTML(
58 + 'beforeend',
59 + `<button type="button"
60 + class="lp-btn-generate-with-ai lp-btn-ai-style"
61 + data-template="#lp-tmpl-edit-description-ai">
62 + <i class="lp-ico-ai"></i><span>Generate description with AI</span>
63 + </button>`,
64 + );
65 + } );
66 + lpUtils.lpOnElementReady( '#postimagediv', ( el ) => {
67 + const elInside = el.querySelector( '.postbox-header' );
68 + elInside.insertAdjacentHTML(
69 + 'afterend',
70 + `<button type="button"
71 + class="lp-btn-generate-with-ai lp-btn-ai-style"
72 + data-template="#lp-tmpl-edit-image-ai">
73 + <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
74 + </button>`
75 + );
76 + } );
77 +
78 + // Check is layout Gutenberg
79 + if ( wp.data && wp.data.select( 'core/editor' ) ) {
80 + isLayoutGutenberg = true;
81 + selectGutenberg = wp.data.select;
82 + dispatchGutenberg = wp.data.dispatch;
83 + editorGutenberg = selectGutenberg( 'core/editor' );
84 +
85 + // For layout Gutenberg - button for title
86 + lpUtils.lpOnElementReady( '.editor-document-bar', ( el ) => {
87 + el.insertAdjacentHTML(
88 + 'afterend',
89 + `<button type="button"
90 + style="margin-left: 5px"
91 + class="lp-btn-generate-with-ai"
92 + data-template="#lp-tmpl-edit-title-ai">
93 + <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
94 + </button>`
95 + );
96 + } );
97 +
98 + // For layout Gutenberg - button for description
99 + lpUtils.lpOnElementReady( '.editor-post-featured-image', ( el ) => {
100 + el.insertAdjacentHTML(
101 + 'beforebegin',
102 + `<button type="button"
103 + class="lp-btn-generate-with-ai"
104 + data-template="#lp-tmpl-edit-description-ai">
105 + <i class="lp-ico-ai"></i><span>Generate description with AI</span>
106 + </button>`
107 + );
108 + } );
109 +
110 + // For layout Gutenberg - button for image
111 + lpUtils.lpOnElementReady( '.editor-post-featured-image', ( el ) => {
112 + el.insertAdjacentHTML(
113 + 'afterend',
114 + `<button type="button"
115 + class="lp-btn-generate-with-ai"
116 + data-template="#lp-tmpl-edit-image-ai">
117 + <i class="lp-ico-ai"></i><span>${ lpData.i18n.generate_with_ai }</span>
118 + </button>`
119 + );
120 + } );
121 + }
122 + }
123 +
124 + this.events();
125 + }
126 +
127 + getTitleInputSelector() {
128 + return this.options.isCourseBuilder
129 + ? this.options.courseBuilderTitleInputSelector
130 + : this.options.titleInputSelector;
131 + }
132 +
133 + getEditorId() {
134 + return this.options.isCourseBuilder
135 + ? this.options.editorIdCourseBuilder
136 + : this.options.editorIdClassic;
137 + }
138 +
139 + setCourseBuilderPostId( form ) {
140 + if ( ! this.options.isCourseBuilder || ! form ) {
141 + return;
142 + }
143 +
144 + const postIdInput = form.querySelector( '[name="post-id"]' );
145 + if ( ! postIdInput ) {
146 + return;
147 + }
148 +
149 + const courseEditWrap = document.querySelector( '.cb-section__course-edit[data-course-id]' );
150 + const courseId = courseEditWrap?.dataset?.courseId || '';
151 + if ( courseId ) {
152 + postIdInput.value = courseId;
153 + }
154 + }
155 +
156 + extractImageSourceFromHtml( html ) {
157 + if ( ! html ) {
158 + return '';
159 + }
160 +
161 + const tempWrapper = document.createElement( 'div' );
162 + tempWrapper.innerHTML = html;
163 + return tempWrapper.querySelector( 'img' )?.getAttribute( 'src' ) || '';
164 + }
165 +
166 + applyCourseBuilderFeaturedImage( attachmentId, imageSrc ) {
167 + if ( ! attachmentId || ! imageSrc ) {
168 + return false;
169 + }
170 +
171 + document.dispatchEvent(
172 + new CustomEvent( 'lp-course-builder/ai-featured-image-applied', {
173 + detail: {
174 + attachmentId,
175 + imageSrc,
176 + },
177 + } )
178 + );
179 +
180 + return true;
181 + }
182 +
183 + events() {
184 + lpUtils.eventHandlers( 'click', [
185 + {
186 + selector: GenerateWithOpenai.selectors.elBtnGenerateWithAi,
187 + class: this,
188 + callBack: this.showPopup.name,
189 + },
190 + {
191 + selector: '.lp-btn-step',
192 + class: this,
193 + callBack: this.showStep.name,
194 + },
195 + {
196 + selector: '.lp-btn-generate-prompt',
197 + class: this,
198 + callBack: this.generatePrompt.name,
199 + },
200 + {
201 + selector: '.lp-btn-call-open-ai',
202 + class: this,
203 + callBack: this.generateData.name,
204 + },
205 + {
206 + selector: '.lp-btn-copy',
207 + class: this,
208 + callBack: this.copyGeneratedData.name,
209 + },
210 + {
211 + selector: '.lp-btn-apply',
212 + class: this,
213 + callBack: this.applyGeneratedData.name,
214 + },
215 + {
216 + selector: '.lp-btn-ai-apply-image',
217 + class: this,
218 + callBack: this.applyImageData.name,
219 + },
220 + {
221 + selector: '.lp-btn-close-ai-popup',
222 + //class: this,
223 + callBack: ( args ) => {
224 + const { e, target } = args;
225 +
226 + const message = lpData.i18n.confirm_close_ai;
227 +
228 + if ( ! lp_is_generating_course_data ) {
229 + SweetAlert.close();
230 + } else if ( confirm( message ) ) {
231 + SweetAlert.close();
232 + }
233 +
234 + // Testing custom confirm box
235 + /*if ( confirm( message ) ) {
236 + SweetAlert.close();
237 + }*/
238 + },
239 + },
240 + ] );
241 + }
242 +
243 + showPopup( args ) {
244 + const { e, target } = args;
245 + const templateId =
246 + target.dataset.template ||
247 + target.closest( '.lp-btn-generate-with-ai' ).dataset.template ||
248 + target.closest( '.lp-generate-data-ai-wrap' ).dataset.template ||
249 + '';
250 +
251 + const modalTemplate = document.querySelector( templateId );
252 +
253 + if ( ! modalTemplate ) {
254 + console.error( `Template ${ templateId } not found!` );
255 + return;
256 + }
257 +
258 + SweetAlert.fire( {
259 + html: modalTemplate.innerHTML,
260 + width: '60%',
261 + showCloseButton: false,
262 + showConfirmButton: false,
263 + allowOutsideClick: false,
264 + allowEscapeKey: false,
265 + didOpen: () => {
266 + popupSweetAlert = SweetAlert.getPopup();
267 + // Click to show tomSelect style
268 + popupSweetAlert.click();
269 +
270 + // Set post title and post content to hidden fields of form to AI prompt reference
271 + const elPostTitleInput = document.querySelector( this.getTitleInputSelector() );
272 + let post_title = '';
273 + if ( elPostTitleInput ) {
274 + post_title = elPostTitleInput.value;
275 + } else if ( isLayoutGutenberg ) {
276 + const elGutenbergTitle = document.querySelector( '.editor-post-card-panel__title-name' );
277 + if ( elGutenbergTitle ) {
278 + post_title = elGutenbergTitle.textContent;
279 + }
280 + }
281 +
282 + let post_content = '';
283 + if ( ! isLayoutGutenberg ) {
284 + const editorId = this.getEditorId();
285 + if ( ! window.tinymce || ! window.tinymce.get( editorId ) ) {
286 + const editorElement = document.querySelector( `#${ editorId }` );
287 + post_content = editorElement ? editorElement.value : '';
288 + } else {
289 + post_content = window.tinymce.get( editorId ).getContent( { format: 'text' } );
290 + }
291 + } else {
292 + const content = editorGutenberg.getEditedPostContent();
293 + post_content = content.replace( /(<([^>]+)>)/gi, '' ); // Remove HTML tags
294 + }
295 +
296 + const form = popupSweetAlert.querySelector( 'form' );
297 + this.setCourseBuilderPostId( form );
298 + const elPostTitle = form.querySelector( '[name=post-title]' );
299 + if ( elPostTitle ) {
300 + elPostTitle.value = post_title;
301 +
302 + if ( ! post_title ) {
303 + const elGroup = elPostTitle.closest( '.form-group' );
304 + const elReferWarning = elGroup.querySelector( '.lp-ai-warning-refer' );
305 + if ( elReferWarning ) {
306 + lpUtils.lpShowHideEl( elReferWarning, 1 );
307 + }
308 + }
309 + }
310 +
311 + const elPostContent = form.querySelector( '[name=post-content]' );
312 + if ( elPostContent ) {
313 + elPostContent.value = post_content;
314 +
315 + if ( post_content.length < 2 ) {
316 + const elGroup = elPostContent.closest( '.form-group' );
317 + const elReferWarning = elGroup.querySelector( '.lp-ai-warning-refer' );
318 + if ( elReferWarning ) {
319 + lpUtils.lpShowHideEl( elReferWarning, 1 );
320 + }
321 + }
322 + }
323 +
324 + const targetAudience = popupSweetAlert.querySelector( 'select[name="target_audience"]' );
325 + if ( targetAudience ) {
326 + if ( lp_course_ai_setting?.target_audience ) {
327 + targetAudience.tomselect.setValue( lp_course_ai_setting.target_audience );
328 + }
329 +
330 + targetAudience.addEventListener( 'change', ( event ) => {
331 + lp_course_ai_setting.target_audience = targetAudience.tomselect.getValue();
332 + localStorage.setItem( 'lp_course_ai_setting', JSON.stringify( lp_course_ai_setting ) );
333 + } );
334 + }
335 +
336 + const tone = popupSweetAlert.querySelector( 'select[name="tone"]' );
337 + if ( tone ) {
338 + if ( lp_course_ai_setting?.tone ) {
339 + tone.tomselect.setValue( lp_course_ai_setting.tone );
340 + }
341 +
342 + tone.addEventListener( 'change', ( event ) => {
343 + lp_course_ai_setting.tone = tone.tomselect.getValue();
344 + localStorage.setItem( 'lp_course_ai_setting', JSON.stringify( lp_course_ai_setting ) );
345 + } );
346 + }
347 +
348 + const language = popupSweetAlert.querySelector( 'select[name="language"]' );
349 + if ( language ) {
350 + if ( lp_course_ai_setting?.language ) {
351 + language.tomselect.setValue( lp_course_ai_setting.language );
352 + }
353 +
354 + language.addEventListener( 'change', ( event ) => {
355 + const value = language.tomselect.getValue();
356 + lp_course_ai_setting.language = value ? [ value ] : [];
357 + localStorage.setItem( 'lp_course_ai_setting', JSON.stringify( lp_course_ai_setting ) );
358 + } );
359 + }
360 + },
361 + } ).then( ( result ) => {
362 + if ( result.isDismissed ) {
363 + if ( lp_is_generating_course_data ) {
364 + lp_is_generating_course_data = false;
365 + }
366 + }
367 + } );
368 + }
369 +
370 + showStep( args ) {
371 + const { e, target } = args;
372 + e.preventDefault();
373 +
374 + const elBtnActions = target.closest( '.button-actions' );
375 + const elCreateCourseAIWrap = elBtnActions.closest(
376 + GenerateWithOpenai.selectors.elGenerateDataAiWrap
377 + );
378 + let step = parseInt( elBtnActions.dataset.step );
379 +
380 + const stepAction = target.dataset.action;
381 + if ( stepAction === 'next' ) {
382 + step++;
383 + } else if ( stepAction === 'prev' ) {
384 + step--;
385 + }
386 +
387 + elBtnActions.dataset.step = step;
388 + const elForm = target.closest( 'form' );
389 + const elContentStep = elForm.querySelector( `.step-content[data-step="${ step }"]` );
390 + const elItemStep = elCreateCourseAIWrap.querySelector( `.step-item[data-step="${ step }"]` );
391 + elForm.querySelectorAll( '.step-content' ).forEach( ( el ) => el.classList.remove( 'active' ) );
392 + elContentStep.classList.add( 'active' );
393 + elCreateCourseAIWrap
394 + .querySelectorAll( '.step-item' )
395 + .forEach( ( el ) => el.classList.remove( 'active' ) );
396 + elItemStep.classList.add( 'active' );
397 +
398 + // Get all buttons step to show/hide
399 + const form = target.closest( 'form' );
400 + const elBtnSteps = form.querySelectorAll( 'button[data-step-show]' );
401 + elBtnSteps.forEach( ( el ) => {
402 + const stepsShow = el.dataset.stepShow.split( ',' ).map( ( s ) => parseInt( s.trim() ) );
403 + if ( stepsShow.includes( step ) ) {
404 + lpUtils.lpShowHideEl( el, 1 );
405 + } else {
406 + lpUtils.lpShowHideEl( el, 0 );
407 + }
408 + } );
409 + }
410 +
411 + /**
412 + * Create prompt from data config
413 + * @param args
414 + */
415 + generatePrompt( args ) {
416 + const { e, target } = args;
417 + e.preventDefault();
418 + lpUtils.lpSetLoadingEl( target, true );
419 +
420 + // Get dataSend
421 + const form = target.closest( 'form' );
422 + let dataSend = JSON.parse( target.dataset.send );
423 + dataSend = lpUtils.mergeDataWithDatForm( form, dataSend );
424 +
425 + // Ajax to generate prompt
426 + const callBack = {
427 + success: ( response ) => {
428 + const { message, status, data } = response;
429 +
430 + lpToastify.show( message, status );
431 +
432 + if ( status === 'success' ) {
433 + const elPromptTextarea = form.querySelector(
434 + 'textarea[name=lp-openai-prompt-generated-field]'
435 + );
436 + elPromptTextarea.value = data;
437 + const elBtnNext = form.querySelector( '.lp-btn-step[data-action=next]' );
438 + elBtnNext.click();
439 + }
440 + },
441 + error: ( error ) => {
442 + lpToastify.show( error, 'error' );
443 + },
444 + completed: () => {
445 + lpUtils.lpSetLoadingEl( target, false );
446 + },
447 + };
448 +
449 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
450 + }
451 +
452 + /**
453 + * Submit prompt to OpenAI to generate course data
454 + * @param args
455 + */
456 + generateData( args ) {
457 + const { e, target } = args;
458 + e.preventDefault();
459 + lpUtils.lpSetLoadingEl( target, true );
460 +
461 + // Get dataSend
462 + const form = target.closest( 'form' );
463 + let dataSend = JSON.parse( target.dataset.send );
464 + dataSend = lpUtils.mergeDataWithDatForm( form, dataSend );
465 +
466 + const btnPrev = form.querySelector( '.lp-btn-step[data-action=prev]' );
467 + lpUtils.lpShowHideEl( btnPrev, 0 );
468 +
469 + // Ajax to generate prompt
470 + const callBack = {
471 + success: ( response ) => {
472 + const { message, status, data } = response;
473 + if ( lp_is_generating_course_data ) {
474 + lpToastify.show( message, status );
475 + }
476 +
477 + if ( status === 'success' ) {
478 + // Save structure data
479 + lp_structure_course = data.lp_structure_course;
480 +
481 + // Set preview HTML
482 + const elResults = form.querySelector( '.lp-ai-generated-results' );
483 + elResults.innerHTML = data.lp_html_preview;
484 +
485 + const elBtnNext = form.querySelector( '.lp-btn-step[data-action=next]' );
486 + elBtnNext.click();
487 + }
488 + },
489 + error: ( error ) => {
490 + lpToastify.show( error, 'error' );
491 + },
492 + completed: () => {
493 + lpUtils.lpSetLoadingEl( target, false );
494 + lpUtils.lpShowHideEl( btnPrev, 1 );
495 + lp_is_generating_course_data = false;
496 + },
497 + };
498 +
499 + lp_is_generating_course_data = true;
500 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
501 + }
502 +
503 + applyGeneratedData( args ) {
504 + const { e, target } = args;
505 + e.preventDefault();
506 +
507 + const dataApply = target.dataset.apply;
508 + const dataTarget = target.dataset.target;
509 +
510 + if ( dataTarget ) {
511 + if ( ! isLayoutGutenberg ) {
512 + if ( dataTarget === 'set-wp-editor-content' ) {
513 + this.setWPEditorContent( dataApply );
514 + } else if ( dataTarget === 'set-wp-title' ) {
515 + const elTitleInput = document.querySelector( this.getTitleInputSelector() );
516 + if ( elTitleInput ) {
517 + elTitleInput.value = dataApply;
518 + elTitleInput.dispatchEvent( new Event( 'input', { bubbles: true } ) );
519 + }
520 + }
521 + } else if ( dataTarget === 'set-wp-editor-content' ) {
522 + dispatchGutenberg( 'core/editor' ).editPost( { content: dataApply } );
523 + } else if ( dataTarget === 'set-wp-title' ) {
524 + dispatchGutenberg( 'core/editor' ).editPost( { title: dataApply } );
525 + }
526 + }
527 +
528 + if ( popupSweetAlert ) {
529 + SweetAlert.close();
530 + }
531 + }
532 +
533 + copyGeneratedData( args ) {
534 + const { e, target } = args;
535 + e.preventDefault();
536 +
537 + const dataCopy = target.dataset.copy;
538 +
539 + if ( navigator.clipboard ) {
540 + navigator.clipboard
541 + .writeText( dataCopy )
542 + .then( () => {
543 + lpToastify.show( 'Copied to clipboard!', 'success' );
544 + } )
545 + .catch( ( err ) => {
546 + lpToastify.show( 'Failed to copy text: ' + err, 'error' );
547 + } );
548 + } else {
549 + // Fallback when clipboard API is unavailable
550 + const textarea = document.createElement( 'textarea' );
551 + textarea.value = dataCopy;
552 + textarea.style.position = 'fixed';
553 + textarea.style.left = '-9999px';
554 + document.body.appendChild( textarea );
555 + textarea.select();
556 + try {
557 + const successful = document.execCommand( 'copy' );
558 + lpToastify.show(
559 + successful ? 'Copied to clipboard!' : 'Failed to copy text',
560 + successful ? 'success' : 'error'
561 + );
562 + } catch ( err ) {
563 + lpToastify.show( 'Failed to copy text: ' + err, 'error' );
564 + }
565 + document.body.removeChild( textarea );
566 + }
567 +
568 + /*navigator.clipboard.writeText( dataCopy ).then( () => {
569 + lpToastify.show( 'Copied to clipboard!', 'success' );
570 + } ).catch( ( err ) => {
571 + lpToastify.show( 'Failed to copy text: ' + err, 'error' );
572 + } );*/
573 + }
574 +
575 + setWPEditorContent( htmlContent ) {
576 + const editorId = this.getEditorId();
577 + const editor = window.tinymce?.get( editorId );
578 +
579 + if ( editor ) {
580 + editor.setContent( htmlContent );
581 + return;
582 + }
583 +
584 + const editorElement = document.querySelector( `#${ editorId }` );
585 + if ( editorElement ) {
586 + editorElement.value = htmlContent;
587 + editorElement.dispatchEvent( new Event( 'input', { bubbles: true } ) );
588 + }
589 + }
590 +
591 + applyImageData( args ) {
592 + const { e, target } = args;
593 + let dataSend = JSON.parse( target.dataset.send );
594 + dataSend = lpUtils.mergeDataWithDatForm( target.closest( 'form' ), dataSend );
595 + //e.preventDefault();
596 + lpUtils.lpSetLoadingEl( target, true );
597 +
598 + // Ajax to generate prompt
599 + const callBack = {
600 + success: ( response ) => {
601 + const { message, status, data } = response;
602 +
603 + lpToastify.show( message, status );
604 +
605 + if ( status === 'success' ) {
606 + if ( this.options.isCourseBuilder ) {
607 + const attachmentId = parseInt( data?.attachment_id || 0, 10 );
608 + const imageSrc = this.extractImageSourceFromHtml( data?.html_image || '' );
609 + const applied = this.applyCourseBuilderFeaturedImage( attachmentId, imageSrc );
610 +
611 + if ( ! applied ) {
612 + lpToastify.show( 'Failed to apply image to course featured image.', 'error' );
613 + return;
614 + }
615 + } else if ( ! isLayoutGutenberg ) {
616 + // Set image
617 + const elImagePreview = document.querySelector( '#postimagediv .inside' );
618 + elImagePreview.outerHTML = data.html_image;
619 + } else {
620 + dispatchGutenberg( 'core/editor' ).editPost( { featured_media: data.attachment_id } );
621 + }
622 +
623 + if ( popupSweetAlert ) {
624 + SweetAlert.close();
625 + }
626 + }
627 + },
628 + error: ( error ) => {
629 + lpToastify.show( error, 'error' );
630 + },
631 + completed: () => {
632 + lpUtils.lpSetLoadingEl( target, false );
633 + },
634 + };
635 +
636 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
637 + }
638 +}