PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.3
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.3
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 / frontend / ai-assistant.js

ai-assistant.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.3, at assets/src/js/frontend/ai-assistant.js

696 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * LP AI Assistant - frontend chat widget.
3 *
4 * LearnPress runtime implementation:
5 * - ES6 class module.
6 * - Delegated events via lpUtils.eventHandlers.
7 * - Boot via lpUtils.lpOnElementReady.
8 * - AJAX transport via window.lpAJAXG.fetchAJAX.
9 *
10 * @since 4.3.5
11 * @version 1.0.0
12 */
13 import * as lpUtils from '../utils.js';
14 import SweetAlert from 'sweetalert2';
15
16 export class AIAssistantWidget {
17 constructor() {
18 this.config = null;
19 this.root = null;
20 this.elements = {};
21 this.storageKey = '';
22 this.history = [];
23 this.activeQuizState = null;
24 this.isRequesting = false;
25 this.quizHookBound = false;
26 this.lastQuizReviewSignature = '';
27 }
28
29 static selectors = {
30 root: '#lp-ai-assistant',
31 toggleBtn: '.lp-ai-assistant__toggle',
32 panel: '.lp-ai-assistant__panel',
33 closeBtn: '.lp-ai-assistant__close-btn',
34 clearBtn: '.lp-ai-assistant__clear-btn',
35 msgList: '.lp-ai-assistant__messages',
36 inputEl: '.lp-ai-assistant__input',
37 sendBtn: '.lp-ai-assistant__send-btn',
38 quickBtn: '.lp-ai-assistant__quick-btn',
39 quickActions: '.lp-ai-assistant__quick-actions',
40 inputArea: '.lp-ai-assistant__input-area',
41 smartReviewBtn: '.lp-ai-assistant__smart-review-btn',
42 quizCard: '.lp-ai-assistant__quiz-card',
43 quizOptionBtn: '.lp-ai-assistant__quiz-option',
44 };
45
46 init() {
47 if ( ! this.validateConfig() ) {
48 return;
49 }
50
51 this.root = document.querySelector( AIAssistantWidget.selectors.root );
52 if ( ! this.root ) {
53 return;
54 }
55
56 this.cacheElements();
57 if ( ! this.validateDOM() ) {
58 return;
59 }
60
61 this.storageKey = `lp_ai_chat_${ this.config.context }_${ this.config.itemId }`;
62 this.applyInitialState();
63 this.loadHistory();
64 this.renderHistoryToDOM();
65 this.bindQuizCompletedHook();
66 this.events();
67 }
68
69 validateConfig() {
70 if ( typeof window.lpAIAssistant !== 'object' || ! window.lpAIAssistant ) {
71 return false;
72 }
73
74 this.config = window.lpAIAssistant;
75 if ( ! this.config.enabled ) {
76 return false;
77 }
78
79 const requiredString = [ 'nonce', 'ajaxUrl' ];
80 for ( const key of requiredString ) {
81 if ( typeof this.config[ key ] !== 'string' || ! this.config[ key ] ) {
82 return false;
83 }
84 }
85
86 const itemId = Number.isInteger( this.config.itemId ) ? this.config.itemId : this.config.lessonId;
87 if ( ! Number.isInteger( itemId ) || itemId <= 0 ) {
88 return false;
89 }
90
91 if ( ! Number.isInteger( this.config.courseId ) || this.config.courseId <= 0 ) {
92 return false;
93 }
94
95 this.config.itemId = itemId;
96 this.config.lessonId = itemId; // Backward compatibility for existing AJAX contract.
97 this.config.context = this.config.context === 'quiz' ? 'quiz' : 'lesson';
98 this.config.quizCompleted = !! this.config.quizCompleted;
99 this.config.enabledActions = {
100 summarize: true,
101 explain: true,
102 quick_quiz: true,
103 smart_review: true,
104 ...( this.config.enabledActions || {} ),
105 };
106
107 this.config.i18n = {
108 you: this.config?.i18n?.you || 'You',
109 assistant: this.config?.i18n?.assistant || 'AI Assistant',
110 thinking: this.config?.i18n?.thinking || 'Thinking...',
111 sendError: this.config?.i18n?.sendError || 'An error occurred. Please try again.',
112 clearConfirm: this.config?.i18n?.clearConfirm || 'Clear chat history?',
113 explainPrompt: this.config?.i18n?.explainPrompt || 'Explain a concept from this lesson.',
114 quizPrompt: this.config?.i18n?.quizPrompt || 'Create a quick quiz from this lesson.',
115 summarizePrompt: this.config?.i18n?.summarizePrompt || 'Summarize this lesson with key points.',
116 smartReviewPrompt: this.config?.i18n?.smartReviewPrompt || 'Give me a smart review of my quiz results.',
117 quizCorrectTitle: this.config?.i18n?.quizCorrectTitle || 'Correct!',
118 quizWrongTitle: this.config?.i18n?.quizWrongTitle || 'Not correct!',
119 };
120
121 return true;
122 }
123
124 cacheElements() {
125 this.elements.toggleBtn = document.querySelector( AIAssistantWidget.selectors.toggleBtn );
126 this.elements.panel = this.root.querySelector( AIAssistantWidget.selectors.panel );
127 this.elements.closeBtn = this.root.querySelector( AIAssistantWidget.selectors.closeBtn );
128 this.elements.clearBtn = this.root.querySelector( AIAssistantWidget.selectors.clearBtn );
129 this.elements.msgList = this.root.querySelector( AIAssistantWidget.selectors.msgList );
130 this.elements.inputEl = this.root.querySelector( AIAssistantWidget.selectors.inputEl );
131 this.elements.sendBtn = this.root.querySelector( AIAssistantWidget.selectors.sendBtn );
132 this.elements.inputArea = this.root.querySelector( AIAssistantWidget.selectors.inputArea );
133 this.elements.quickActions = this.root.querySelector( AIAssistantWidget.selectors.quickActions );
134 this.elements.smartReviewBtn = this.root.querySelector( AIAssistantWidget.selectors.smartReviewBtn );
135 }
136
137 validateDOM() {
138 // inputEl and sendBtn are optional — absent when free chat is disabled.
139 return !! (
140 this.elements.toggleBtn &&
141 this.elements.panel &&
142 this.elements.msgList
143 );
144 }
145
146 applyInitialState() {
147 if ( this.elements.smartReviewBtn ) {
148 const showSmartReview = this.config.context === 'quiz'
149 ? this.config.quizCompleted
150 : !! this.config.enabledActions?.smart_review;
151 this.elements.smartReviewBtn.hidden = ! showSmartReview;
152 }
153
154 this.setQuizInputMode( false );
155 }
156
157 bindQuizCompletedHook() {
158 if ( this.config.context === 'quiz' ) {
159 return;
160 }
161
162 if ( this.quizHookBound || ! this.elements.smartReviewBtn ) {
163 return;
164 }
165
166 const hooks = window?.wp?.hooks;
167 if ( ! hooks || typeof hooks.addAction !== 'function' ) {
168 return;
169 }
170
171 hooks.addAction( 'lp-js-quiz-answer', 'learnpress/ai-assistant-smart-review', ( answered, status ) => {
172 if ( String( status || '' ).toLowerCase() !== 'completed' ) {
173 return;
174 }
175
176 this.config.quizCompleted = true;
177 this.elements.smartReviewBtn.hidden = false;
178 } );
179
180 this.quizHookBound = true;
181 }
182
183 events() {
184 if ( AIAssistantWidget._loadedEvents ) {
185 return;
186 }
187 AIAssistantWidget._loadedEvents = this;
188
189 lpUtils.eventHandlers( 'click', [
190 {
191 selector: AIAssistantWidget.selectors.toggleBtn,
192 class: this,
193 callBack: this.handleToggleClick.name,
194 },
195 {
196 selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.closeBtn }`,
197 class: this,
198 callBack: this.handleCloseClick.name,
199 },
200 {
201 selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.clearBtn }`,
202 class: this,
203 callBack: this.handleClearClick.name,
204 },
205 {
206 selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.sendBtn }`,
207 class: this,
208 callBack: this.handleSendClick.name,
209 },
210 {
211 selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.quickBtn }`,
212 class: this,
213 callBack: this.handleQuickActionClick.name,
214 },
215 {
216 selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.quizOptionBtn }`,
217 class: this,
218 callBack: this.handleQuizOptionClick.name,
219 },
220 ] );
221
222 lpUtils.eventHandlers( 'keydown', [
223 {
224 selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.inputEl }`,
225 class: this,
226 callBack: this.handleInputKeydown.name,
227 },
228 {
229 selector: 'body',
230 class: this,
231 callBack: this.handleEscapeKeydown.name,
232 },
233 ] );
234 }
235
236 handleToggleClick( args ) {
237 args.e.preventDefault();
238 if ( this.elements.panel.hidden ) {
239 this.openPanel();
240 } else {
241 this.closePanel();
242 }
243 }
244
245 handleCloseClick( args ) {
246 args.e.preventDefault();
247 this.closePanel();
248 }
249
250 handleClearClick( args ) {
251 args.e.preventDefault();
252 SweetAlert.fire( {
253 title: this.config.i18n.clearConfirm,
254 icon: 'warning',
255 showCancelButton: true,
256 confirmButtonColor: 'var(--lp-primary-color, #ffb606)',
257 } ).then( ( result ) => {
258 if ( result.isConfirmed ) {
259 this.clearHistory();
260 }
261 } );
262 }
263
264 handleSendClick( args ) {
265 args.e.preventDefault();
266 this.sendMessage( this.elements.inputEl?.value ?? '' );
267 }
268
269 handleQuickActionClick( args ) {
270 args.e.preventDefault();
271
272 if ( this.activeQuizState?.is_active ) {
273 return;
274 }
275
276 const btn = args.target.closest( AIAssistantWidget.selectors.quickBtn );
277 if ( ! btn ) {
278 return;
279 }
280
281 const action = btn.dataset.lpAiAction;
282 const prompts = {
283 explain: this.config.i18n.explainPrompt,
284 'quick-quiz': this.config.i18n.quizPrompt,
285 summarize: this.config.i18n.summarizePrompt,
286 'smart-review': this.config.i18n.smartReviewPrompt,
287 };
288
289 const prompt = prompts[ action ];
290 if ( ! prompt ) {
291 return;
292 }
293
294 this.openPanel();
295 this.sendMessage( prompt, action );
296 }
297
298 handleQuizOptionClick( args ) {
299 args.e.preventDefault();
300 if ( this.isRequesting || ! this.activeQuizState?.is_active ) {
301 return;
302 }
303
304 const btn = args.target.closest( AIAssistantWidget.selectors.quizOptionBtn );
305 if ( ! btn ) {
306 return;
307 }
308
309 const answerText = ( btn.dataset.option || btn.textContent || '' ).trim();
310 if ( ! answerText ) {
311 return;
312 }
313
314 this.sendMessage( answerText );
315 }
316
317 handleInputKeydown( args ) {
318 if ( this.activeQuizState?.is_active ) {
319 return;
320 }
321
322 if ( args.e.key === 'Enter' && ! args.e.shiftKey ) {
323 args.e.preventDefault();
324 this.sendMessage( this.elements.inputEl?.value ?? '' );
325 }
326 }
327
328 handleEscapeKeydown( args ) {
329 if ( args.e.key !== 'Escape' ) {
330 return;
331 }
332
333 if ( this.elements.panel && ! this.elements.panel.hidden ) {
334 this.closePanel();
335 }
336 }
337
338 getAjaxHandle() {
339 const ajaxHandle = window.lpAJAXG;
340 if ( ! ajaxHandle || typeof ajaxHandle.fetchAJAX !== 'function' ) {
341 return null;
342 }
343
344 return ajaxHandle;
345 }
346
347 openPanel() {
348 this.elements.panel.hidden = false;
349 this.root.setAttribute( 'aria-hidden', 'false' );
350 this.elements.toggleBtn.setAttribute( 'aria-expanded', 'true' );
351 this.elements.toggleBtn.classList.add( 'is-hidden' );
352 this.elements.inputEl?.focus();
353
354 if ( this.elements.msgList ) {
355 this.elements.msgList.scrollTop = this.elements.msgList.scrollHeight;
356 }
357 }
358
359 closePanel() {
360 this.elements.panel.hidden = true;
361 this.root.setAttribute( 'aria-hidden', 'true' );
362 this.elements.toggleBtn.setAttribute( 'aria-expanded', 'false' );
363 this.elements.toggleBtn.classList.remove( 'is-hidden' );
364 this.elements.toggleBtn.focus();
365 }
366
367 setLoadingState( isLoading ) {
368 this.isRequesting = isLoading;
369 if ( this.elements.sendBtn ) {
370 this.elements.sendBtn.disabled = isLoading;
371 }
372 if ( this.elements.inputEl ) {
373 this.elements.inputEl.disabled = isLoading;
374 }
375 }
376
377 setQuizInputMode( isQuizActive ) {
378 if ( this.elements.inputArea ) {
379 this.elements.inputArea.classList.toggle( 'lp-ai-assistant__input-area--hidden', isQuizActive );
380 }
381
382 if ( this.elements.quickActions ) {
383 this.elements.quickActions.classList.toggle( 'lp-ai-assistant__quick-actions--disabled', isQuizActive );
384 }
385 }
386
387 loadHistory() {
388 try {
389 const raw = localStorage.getItem( this.storageKey );
390 this.history = raw ? JSON.parse( raw ) : [];
391 if ( ! Array.isArray( this.history ) ) {
392 this.history = [];
393 }
394
395 const lastReview = [ ...this.history ]
396 .reverse()
397 .find( ( item ) => item?.type === 'quiz_review' && item?.review );
398 this.lastQuizReviewSignature = lastReview?.review
399 ? this.getQuizReviewKey( lastReview.review )
400 : '';
401 } catch ( _e ) {
402 this.history = [];
403 this.lastQuizReviewSignature = '';
404 }
405 }
406
407 saveHistory() {
408 try {
409 localStorage.setItem( this.storageKey, JSON.stringify( this.history ) );
410 } catch ( _e ) {
411 // Ignore storage errors.
412 }
413 }
414
415 clearHistory() {
416 this.history = [];
417 this.activeQuizState = null;
418 this.lastQuizReviewSignature = '';
419 this.elements.msgList.innerHTML = '';
420 localStorage.removeItem( this.storageKey );
421 this.setQuizInputMode( false );
422 }
423
424 escHtml( text ) {
425 const div = document.createElement( 'div' );
426 div.appendChild( document.createTextNode( String( text ) ) );
427 return div.innerHTML;
428 }
429
430 appendMessage( role, text ) {
431 const el = document.createElement( 'div' );
432 el.className = `lp-ai-assistant__msg lp-ai-assistant__msg--${ role }`;
433
434 const label = role === 'user' ? this.config.i18n.you : this.config.i18n.assistant;
435 el.innerHTML =
436 `<span class="lp-ai-assistant__msg-label">${ this.escHtml( label ) }</span>` +
437 `<p class="lp-ai-assistant__msg-text">${ this.escHtml( text ) }</p>`;
438
439 this.elements.msgList.appendChild( el );
440 this.elements.msgList.scrollTop = this.elements.msgList.scrollHeight;
441 return el;
442 }
443
444 renderHistoryToDOM() {
445 this.elements.msgList.innerHTML = '';
446 this.history.forEach( ( message ) => {
447 if ( message?.type === 'quiz_review' && message?.review ) {
448 this.appendQuizReviewCard( message.review );
449 return;
450 }
451
452 if ( ! message || ! [ 'user', 'assistant' ].includes( message.role ) ) {
453 return;
454 }
455
456 this.appendMessage( message.role, message.content || '' );
457 } );
458
459 this.renderQuizState();
460 }
461
462 getQuizReviewFromState( quiz ) {
463 if ( ! quiz || ! quiz.feedback || typeof quiz.feedback !== 'object' ) {
464 return null;
465 }
466
467 const currentIndex = Number.parseInt( quiz.current_index || 0, 10 );
468 const questionIndex = Math.max( 0, currentIndex - 1 );
469 const question = quiz.questions?.[ questionIndex ];
470 if ( ! question || ! Array.isArray( question.options ) ) {
471 return null;
472 }
473
474 const selectedIndex = Number.parseInt( quiz.feedback.selected_index ?? -1, 10 );
475 const correctIndex = Number.parseInt( quiz.feedback.correct_index ?? -1, 10 );
476
477 return {
478 question_index: questionIndex,
479 total: Number.parseInt( quiz.total || question.options.length || 0, 10 ),
480 question: question.question || '',
481 options: question.options,
482 selected_index: selectedIndex,
483 correct_index: correctIndex,
484 is_correct: !! quiz.feedback.is_correct,
485 explanation: quiz.feedback.explanation || '',
486 };
487 }
488
489 getQuizReviewKey( review ) {
490 return [
491 review.question_index,
492 review.total,
493 review.selected_index,
494 review.correct_index,
495 review.is_correct ? 1 : 0,
496 ].join( '|' );
497 }
498
499 pushQuizReviewToHistory( review ) {
500 const reviewKey = this.getQuizReviewKey( review );
501 if ( reviewKey === this.lastQuizReviewSignature ) {
502 return false;
503 }
504 this.lastQuizReviewSignature = reviewKey;
505
506 this.history.push( {
507 type: 'quiz_review',
508 review,
509 } );
510 this.saveHistory();
511
512 return true;
513 }
514
515 renderQuizReviewOptions( review ) {
516 const options = Array.isArray( review.options ) ? review.options : [];
517
518 return options.map( ( option, index ) => {
519 const letter = String.fromCharCode( 65 + index );
520 const classes = [ 'lp-ai-assistant__quiz-option' ];
521 if ( index === review.correct_index ) {
522 classes.push( 'is-correct-answer' );
523 }
524
525 if ( index === review.selected_index ) {
526 classes.push( review.is_correct ? 'is-selected-correct' : 'is-selected-wrong' );
527 }
528
529 return `<button class="${ classes.join( ' ' ) }" disabled>${ letter }. ${ this.escHtml( option ) }</button>`;
530 } ).join( '' );
531 }
532
533 appendQuizReviewCard( review ) {
534 const card = document.createElement( 'div' );
535 card.className = 'lp-ai-assistant__quiz-card lp-ai-assistant__quiz-card--review';
536 card.setAttribute( 'data-review-key', this.getQuizReviewKey( review ) );
537
538 const feedbackClass = review.is_correct ? 'is-correct' : 'is-wrong';
539 const feedbackTitle = review.is_correct
540 ? this.config.i18n.quizCorrectTitle
541 : this.config.i18n.quizWrongTitle;
542 const feedbackHtml =
543 `<div class="lp-ai-assistant__quiz-feedback ${ feedbackClass }">` +
544 `<strong>${ this.escHtml( feedbackTitle ) }</strong>` +
545 ( review.explanation ? `<div>${ this.escHtml( review.explanation ) }</div>` : '' ) +
546 '</div>';
547
548 card.innerHTML =
549 `<div class="lp-ai-assistant__quiz-head">Question ${ review.question_index + 1 }/${ review.total || review.options.length }</div>` +
550 `<div class="lp-ai-assistant__quiz-question">${ this.escHtml( review.question || '' ) }</div>` +
551 `<div class="lp-ai-assistant__quiz-options">${ this.renderQuizReviewOptions( review ) }</div>` +
552 feedbackHtml;
553
554 this.elements.msgList.appendChild( card );
555 }
556
557 renderQuizState() {
558 const oldActiveQuizCard = this.elements.msgList.querySelector( '.lp-ai-assistant__quiz-card--active' );
559 if ( oldActiveQuizCard ) {
560 oldActiveQuizCard.remove();
561 }
562
563 if ( ! this.activeQuizState || ! this.activeQuizState.questions ) {
564 this.setQuizInputMode( false );
565 return;
566 }
567
568 const quiz = this.activeQuizState;
569 const review = this.getQuizReviewFromState( quiz );
570 if ( review ) {
571 if ( this.pushQuizReviewToHistory( review ) ) {
572 this.appendQuizReviewCard( review );
573 }
574 }
575
576 if ( ! quiz.is_active ) {
577 this.setQuizInputMode( false );
578 return;
579 }
580
581 const currentIndex = Number.parseInt( quiz.current_index || 0, 10 );
582 const question = quiz.questions?.[ currentIndex ];
583 if ( ! question ) {
584 this.setQuizInputMode( false );
585 return;
586 }
587
588 const card = document.createElement( 'div' );
589 card.className = 'lp-ai-assistant__quiz-card lp-ai-assistant__quiz-card--active';
590
591 const options = Array.isArray( question.options ) ? question.options : [];
592 const optionsHtml = options.map( ( option, index ) => {
593 const letter = String.fromCharCode( 65 + index );
594 return `<button class="lp-ai-assistant__quiz-option" data-index="${ index }" data-option="${ this.escHtml( option ) }">${ letter }. ${ this.escHtml( option ) }</button>`;
595 } ).join( '' );
596
597 card.innerHTML =
598 `<div class="lp-ai-assistant__quiz-head">Question ${ currentIndex + 1 }/${ quiz.total || options.length }</div>` +
599 `<div class="lp-ai-assistant__quiz-question">${ this.escHtml( question.question || '' ) }</div>` +
600 `<div class="lp-ai-assistant__quiz-options">${ optionsHtml }</div>`;
601
602 this.elements.msgList.appendChild( card );
603 this.setQuizInputMode( true );
604 }
605
606 scrollToMessageStart( messageEl ) {
607 const msgList = this.elements.msgList;
608 if ( ! msgList || ! messageEl || ! msgList.contains( messageEl ) ) {
609 return;
610 }
611
612 msgList.scrollTop = Math.max( 0, messageEl.offsetTop - 8 );
613 }
614
615 sendMessage( message, actionHint = '' ) {
616 const text = ( message || '' ).trim();
617 if ( this.isRequesting || ! text ) {
618 return;
619 }
620
621 const ajaxHandle = this.getAjaxHandle();
622 if ( ! ajaxHandle ) {
623 this.appendMessage( 'assistant', this.config.i18n.sendError );
624 return;
625 }
626
627 this.appendMessage( 'user', text );
628
629 const contextHistory = this.history.slice();
630 this.history.push( { role: 'user', content: text } );
631 this.saveHistory();
632 if ( this.elements.inputEl ) {
633 this.elements.inputEl.value = '';
634 }
635
636 const pendingEl = this.appendMessage( 'assistant', this.config.i18n.thinking );
637 const pendingTextEl = pendingEl.querySelector( '.lp-ai-assistant__msg-text' );
638 this.setLoadingState( true );
639
640 const dataSend = {
641 action: 'openai_assistant_chat',
642 message: text,
643 item_id: this.config.itemId,
644 course_id: this.config.courseId,
645 history: contextHistory,
646 active_quiz_questions: this.activeQuizState || [],
647 action_hint: typeof actionHint === 'string' ? actionHint : '',
648 };
649
650 const callBack = {
651 success: ( response ) => {
652 if ( response?.status === 'success' && response?.data ) {
653 pendingTextEl.textContent = response.data.message || '';
654
655 if ( response?.data?.type === 'quiz' ) {
656 this.activeQuizState = response?.data?.quiz || null;
657 this.renderQuizState();
658
659 const isQuizCompleted = !! this.activeQuizState?.completed || this.activeQuizState?.is_active === false;
660 if ( isQuizCompleted && this.elements.msgList?.contains( pendingEl ) ) {
661 // Keep completion feedback after the last review card.
662 this.elements.msgList.appendChild( pendingEl );
663 }
664 } else {
665 this.activeQuizState = null;
666 this.renderQuizState();
667 }
668
669 this.history.push( { role: 'assistant', content: response.data.message } );
670 this.saveHistory();
671 } else {
672 this.activeQuizState = null;
673 this.renderQuizState();
674 pendingTextEl.textContent = response?.message || this.config.i18n.sendError;
675 }
676 },
677 error: () => {
678 this.activeQuizState = null;
679 this.renderQuizState();
680 pendingTextEl.textContent = this.config.i18n.sendError;
681 },
682 completed: () => {
683 this.setLoadingState( false );
684 this.scrollToMessageStart( pendingEl );
685 },
686 };
687
688 ajaxHandle.fetchAJAX( dataSend, callBack );
689 }
690 }
691
692 const aiAssistantWidget = new AIAssistantWidget();
693 lpUtils.lpOnElementReady( AIAssistantWidget.selectors.root, () => {
694 aiAssistantWidget.init();
695 } );
696