PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.8
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 4.2.0 All 139 releases
learnpress / assets / src / js / admin / generate-with-openai.js

generate-with-openai.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.8, at assets/src/js/admin/generate-with-openai.js

643 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
643