PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.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.4.8, at assets/src/js/admin/generate-with-openai.js

639 lines 18.0 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 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 }
639