# learnpress/4.4.6/assets/src/js/frontend/ai-assistant.js

LearnPress – WordPress LMS Plugin for Create and Sell Online Courses, version 4.4.6. 774 lines.

- Page: https://pluginprobe.com/plugins/learnpress/4.4.6/code/assets/src/js/frontend/ai-assistant.js
- Raw: https://pluginprobe.com/plugins/learnpress/4.4.6/raw/assets/src/js/frontend/ai-assistant.js
- Modified: 2026-08-31T10:33:54+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/learnpress/4.4.6/code/assets/src/js/frontend/ai-assistant.js#L10-L20`.

```javascript
/**
 * LP AI Assistant - frontend chat widget.
 *
 * LearnPress runtime implementation:
 * - ES6 class module.
 * - Delegated events via lpUtils.eventHandlers.
 * - Boot via lpUtils.lpOnElementReady.
 * - AJAX transport via window.lpAJAXG.fetchAJAX.
 *
 * @since   4.3.5
 * @version 1.0.0
 */
import * as lpUtils from '../utils.js';
import SweetAlert from 'sweetalert2';

export class AIAssistantWidget {
	constructor() {
		this.config = null;
		this.root = null;
		this.elements = {};
		this.storageKey = '';
		this.history = [];
		this.activeQuizState = null;
		this.isRequesting = false;
		this.quizHookBound = false;
		this.lastQuizReviewSignature = '';
	}

	static selectors = {
		root: '#lp-ai-assistant',
		toggleBtn: '.lp-ai-assistant__toggle',
		panel: '.lp-ai-assistant__panel',
		closeBtn: '.lp-ai-assistant__close-btn',
		clearBtn: '.lp-ai-assistant__clear-btn',
		msgList: '.lp-ai-assistant__messages',
		inputEl: '.lp-ai-assistant__input',
		sendBtn: '.lp-ai-assistant__send-btn',
		quickBtn: '.lp-ai-assistant__quick-btn',
		quickActions: '.lp-ai-assistant__quick-actions',
		inputArea: '.lp-ai-assistant__input-area',
		smartReviewBtn: '.lp-ai-assistant__smart-review-btn',
		quizCard: '.lp-ai-assistant__quiz-card',
		quizOptionBtn: '.lp-ai-assistant__quiz-option',
	};

	/**
	 * Curriculum item types the assistant supports.
	 *
	 * Mirrors AIAssistantController::get_supported_item_types(). The server re-validates,
	 * so this only avoids pointless requests.
	 */
	static itemTypes = [ 'lp_lesson', 'lp_quiz' ];

	init() {
		if ( ! this.validateConfig() ) {
			return;
		}

		this.root = document.querySelector( AIAssistantWidget.selectors.root );
		if ( ! this.root ) {
			return;
		}

		this.cacheElements();
		if ( ! this.validateDOM() ) {
			return;
		}

		this.storageKey = `lp_ai_chat_${ this.config.context }_${ this.config.itemId }`;
		this.applyInitialState();
		this.loadHistory();
		this.renderHistoryToDOM();
		this.bindQuizCompletedHook();
		this.events();
	}

	validateConfig() {
		if ( typeof window.lpAIAssistant !== 'object' || ! window.lpAIAssistant ) {
			return false;
		}

		this.config = window.lpAIAssistant;
		if ( ! this.config.enabled ) {
			return false;
		}

		const requiredString = [ 'nonce', 'ajaxUrl' ];
		for ( const key of requiredString ) {
			if ( typeof this.config[ key ] !== 'string' || ! this.config[ key ] ) {
				return false;
			}
		}

		const itemId = Number.isInteger( this.config.itemId ) ? this.config.itemId : this.config.lessonId;
		if ( ! Number.isInteger( itemId ) || itemId <= 0 ) {
			return false;
		}

		if ( ! Number.isInteger( this.config.courseId ) || this.config.courseId <= 0 ) {
			return false;
		}

		/**
		 * itemType is required and must come from the server-localized config. It is
		 * never guessed from context, lessonId or the numeric ID: a course item is
		 * identified by (courseId, itemType, itemId), and inferring one leg of that
		 * tuple on the client would let a lesson request address a quiz record.
		 */
		if ( ! AIAssistantWidget.itemTypes.includes( this.config.itemType ) ) {
			return false;
		}

		this.config.itemId = itemId;
		this.config.lessonId = itemId; // Backward compatibility for existing AJAX contract.
		this.config.context = this.config.context === 'quiz' ? 'quiz' : 'lesson';
		this.config.quizCompleted = !! this.config.quizCompleted;
		this.config.enabledActions = {
			summarize: true,
			explain: true,
			quick_quiz: true,
			smart_review: true,
			...( this.config.enabledActions || {} ),
		};

		this.config.i18n = {
			you: this.config?.i18n?.you || 'You',
			assistant: this.config?.i18n?.assistant || 'AI Assistant',
			thinking: this.config?.i18n?.thinking || 'Thinking...',
			sendError: this.config?.i18n?.sendError || 'An error occurred. Please try again.',
			clearConfirm: this.config?.i18n?.clearConfirm || 'Clear chat history?',
			explainPrompt: this.config?.i18n?.explainPrompt || 'Explain a concept from this lesson.',
			quizPrompt: this.config?.i18n?.quizPrompt || 'Create a quick quiz from this lesson.',
			summarizePrompt: this.config?.i18n?.summarizePrompt || 'Summarize this lesson with key points.',
			smartReviewPrompt: this.config?.i18n?.smartReviewPrompt || 'Give me a smart review of my quiz results.',
			quizCorrectTitle: this.config?.i18n?.quizCorrectTitle || 'Correct!',
			quizWrongTitle: this.config?.i18n?.quizWrongTitle || 'Not correct!',
		};

		return true;
	}

	cacheElements() {
		this.elements.toggleBtn = document.querySelector( AIAssistantWidget.selectors.toggleBtn );
		this.elements.panel = this.root.querySelector( AIAssistantWidget.selectors.panel );
		this.elements.closeBtn = this.root.querySelector( AIAssistantWidget.selectors.closeBtn );
		this.elements.clearBtn = this.root.querySelector( AIAssistantWidget.selectors.clearBtn );
		this.elements.msgList = this.root.querySelector( AIAssistantWidget.selectors.msgList );
		this.elements.inputEl = this.root.querySelector( AIAssistantWidget.selectors.inputEl );
		this.elements.sendBtn = this.root.querySelector( AIAssistantWidget.selectors.sendBtn );
		this.elements.inputArea = this.root.querySelector( AIAssistantWidget.selectors.inputArea );
		this.elements.quickActions = this.root.querySelector( AIAssistantWidget.selectors.quickActions );
		this.elements.smartReviewBtn = this.root.querySelector( AIAssistantWidget.selectors.smartReviewBtn );
	}

	validateDOM() {
		// inputEl and sendBtn are optional — absent when free chat is disabled.
		return !! (
			this.elements.toggleBtn &&
			this.elements.panel &&
			this.elements.msgList
		);
	}

	applyInitialState() {
		if ( this.elements.smartReviewBtn ) {
			const showSmartReview = this.config.context === 'quiz'
				? this.config.quizCompleted
				: !! this.config.enabledActions?.smart_review;
			this.elements.smartReviewBtn.hidden = ! showSmartReview;
		}

		this.setQuizInputMode( false );
	}

	bindQuizCompletedHook() {
		if ( this.config.context === 'quiz' ) {
			return;
		}

		if ( this.quizHookBound || ! this.elements.smartReviewBtn ) {
			return;
		}

		const hooks = window?.wp?.hooks;
		if ( ! hooks || typeof hooks.addAction !== 'function' ) {
			return;
		}

		hooks.addAction( 'lp-js-quiz-answer', 'learnpress/ai-assistant-smart-review', ( answered, status ) => {
			if ( String( status || '' ).toLowerCase() !== 'completed' ) {
				return;
			}

			this.config.quizCompleted = true;
			this.elements.smartReviewBtn.hidden = false;
		} );

		this.quizHookBound = true;
	}

	events() {
		if ( AIAssistantWidget._loadedEvents ) {
			return;
		}
		AIAssistantWidget._loadedEvents = this;

		lpUtils.eventHandlers( 'click', [
			{
				selector: AIAssistantWidget.selectors.toggleBtn,
				class: this,
				callBack: this.handleToggleClick.name,
			},
			{
				selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.closeBtn }`,
				class: this,
				callBack: this.handleCloseClick.name,
			},
			{
				selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.clearBtn }`,
				class: this,
				callBack: this.handleClearClick.name,
			},
			{
				selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.sendBtn }`,
				class: this,
				callBack: this.handleSendClick.name,
			},
			{
				selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.quickBtn }`,
				class: this,
				callBack: this.handleQuickActionClick.name,
			},
			{
				selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.quizOptionBtn }`,
				class: this,
				callBack: this.handleQuizOptionClick.name,
			},
		] );

		lpUtils.eventHandlers( 'keydown', [
			{
				selector: `${ AIAssistantWidget.selectors.root } ${ AIAssistantWidget.selectors.inputEl }`,
				class: this,
				callBack: this.handleInputKeydown.name,
			},
			{
				selector: 'body',
				class: this,
				callBack: this.handleEscapeKeydown.name,
			},
		] );
	}

	handleToggleClick( args ) {
		args.e.preventDefault();
		if ( this.elements.panel.hidden ) {
			this.openPanel();
		} else {
			this.closePanel();
		}
	}

	handleCloseClick( args ) {
		args.e.preventDefault();
		this.closePanel();
	}

	handleClearClick( args ) {
		args.e.preventDefault();
		SweetAlert.fire( {
			title: this.config.i18n.clearConfirm,
			icon: 'warning',
			showCancelButton: true,
			confirmButtonColor: 'var(--lp-primary-color, #ffb606)',
		} ).then( ( result ) => {
			if ( result.isConfirmed ) {
				this.clearHistory();
			}
		} );
	}

	handleSendClick( args ) {
		args.e.preventDefault();
		this.sendMessage( this.elements.inputEl?.value ?? '' );
	}

	handleQuickActionClick( args ) {
		args.e.preventDefault();

		if ( this.activeQuizState?.is_active ) {
			return;
		}

		const btn = args.target.closest( AIAssistantWidget.selectors.quickBtn );
		if ( ! btn ) {
			return;
		}

		const action = btn.dataset.lpAiAction;
		const prompts = {
			explain: this.config.i18n.explainPrompt,
			'quick-quiz': this.config.i18n.quizPrompt,
			summarize: this.config.i18n.summarizePrompt,
			'smart-review': this.config.i18n.smartReviewPrompt,
		};

		const prompt = prompts[ action ];
		if ( ! prompt ) {
			return;
		}

		this.openPanel();
		this.sendMessage( prompt, action );
	}

	handleQuizOptionClick( args ) {
		args.e.preventDefault();
		if ( this.isRequesting || ! this.activeQuizState?.is_active ) {
			return;
		}

		const btn = args.target.closest( AIAssistantWidget.selectors.quizOptionBtn );
		if ( ! btn ) {
			return;
		}

		const answerText = ( btn.dataset.option || btn.textContent || '' ).trim();
		if ( ! answerText ) {
			return;
		}

		this.sendMessage( answerText );
	}

	handleInputKeydown( args ) {
		if ( this.activeQuizState?.is_active ) {
			return;
		}

		if ( args.e.key === 'Enter' && ! args.e.shiftKey ) {
			args.e.preventDefault();
			this.sendMessage( this.elements.inputEl?.value ?? '' );
		}
	}

	handleEscapeKeydown( args ) {
		if ( args.e.key !== 'Escape' ) {
			return;
		}

		if ( this.elements.panel && ! this.elements.panel.hidden ) {
			this.closePanel();
		}
	}

	getAjaxHandle() {
		const ajaxHandle = window.lpAJAXG;
		if ( ! ajaxHandle || typeof ajaxHandle.fetchAJAX !== 'function' ) {
			return null;
		}

		return ajaxHandle;
	}

	openPanel() {
		this.elements.panel.hidden = false;
		this.root.setAttribute( 'aria-hidden', 'false' );
		this.elements.toggleBtn.setAttribute( 'aria-expanded', 'true' );
		this.elements.toggleBtn.classList.add( 'is-hidden' );
		this.elements.inputEl?.focus();

		if ( this.elements.msgList ) {
			this.elements.msgList.scrollTop = this.elements.msgList.scrollHeight;
		}
	}

	closePanel() {
		this.elements.panel.hidden = true;
		this.root.setAttribute( 'aria-hidden', 'true' );
		this.elements.toggleBtn.setAttribute( 'aria-expanded', 'false' );
		this.elements.toggleBtn.classList.remove( 'is-hidden' );
		this.elements.toggleBtn.focus();
	}

	setLoadingState( isLoading ) {
		this.isRequesting = isLoading;
		if ( this.elements.sendBtn ) {
			this.elements.sendBtn.disabled = isLoading;
		}
		if ( this.elements.inputEl ) {
			this.elements.inputEl.disabled = isLoading;
		}
	}

	setQuizInputMode( isQuizActive ) {
		if ( this.elements.inputArea ) {
			this.elements.inputArea.classList.toggle( 'lp-ai-assistant__input-area--hidden', isQuizActive );
		}

		if ( this.elements.quickActions ) {
			this.elements.quickActions.classList.toggle( 'lp-ai-assistant__quick-actions--disabled', isQuizActive );
		}
	}

	loadHistory() {
		try {
			const raw = localStorage.getItem( this.storageKey );
			this.history = raw ? JSON.parse( raw ) : [];
			if ( ! Array.isArray( this.history ) ) {
				this.history = [];
			}

			const lastReview = [ ...this.history ]
				.reverse()
				.find( ( item ) => item?.type === 'quiz_review' && item?.review );
			this.lastQuizReviewSignature = lastReview?.review
				? this.getQuizReviewKey( lastReview.review )
				: '';
		} catch ( _e ) {
			this.history = [];
			this.lastQuizReviewSignature = '';
		}
	}

	saveHistory() {
		try {
			localStorage.setItem( this.storageKey, JSON.stringify( this.history ) );
		} catch ( _e ) {
			// Ignore storage errors.
		}
	}

	clearHistory() {
		this.history = [];
		this.activeQuizState = null;
		this.lastQuizReviewSignature = '';
		this.elements.msgList.innerHTML = '';
		localStorage.removeItem( this.storageKey );
		this.setQuizInputMode( false );
	}

	/**
	 * Create an element with a class and plain text content.
	 *
	 * All assistant content originates from OpenAI output, so it is built through the
	 * DOM API only. Nothing on this path goes through innerHTML: text assigned via
	 * textContent can never become markup, and attributes set via the DOM API can never
	 * break out into a new attribute or event handler.
	 *
	 * @param {string} tag       Tag name.
	 * @param {string} className Class attribute.
	 * @param {string} text      Text content.
	 * @return {HTMLElement} The created element.
	 */
	createEl( tag, className, text = '' ) {
		const el = document.createElement( tag );
		if ( className ) {
			el.className = className;
		}
		if ( text !== '' ) {
			el.textContent = String( text );
		}
		return el;
	}

	appendMessage( role, text ) {
		const el = this.createEl( 'div', `lp-ai-assistant__msg lp-ai-assistant__msg--${ role }` );
		const label = role === 'user' ? this.config.i18n.you : this.config.i18n.assistant;

		el.appendChild( this.createEl( 'span', 'lp-ai-assistant__msg-label', label ) );
		el.appendChild( this.createEl( 'p', 'lp-ai-assistant__msg-text', text ) );

		this.elements.msgList.appendChild( el );
		this.elements.msgList.scrollTop = this.elements.msgList.scrollHeight;
		return el;
	}

	renderHistoryToDOM() {
		this.elements.msgList.innerHTML = '';
		this.history.forEach( ( message ) => {
			if ( message?.type === 'quiz_review' && message?.review ) {
				this.appendQuizReviewCard( message.review );
				return;
			}

			if ( ! message || ! [ 'user', 'assistant' ].includes( message.role ) ) {
				return;
			}

			this.appendMessage( message.role, message.content || '' );
		} );

		this.renderQuizState();
	}

	getQuizReviewFromState( quiz ) {
		if ( ! quiz || ! quiz.feedback || typeof quiz.feedback !== 'object' ) {
			return null;
		}

		const currentIndex = Number.parseInt( quiz.current_index || 0, 10 );
		const questionIndex = Math.max( 0, currentIndex - 1 );
		const question = quiz.questions?.[ questionIndex ];
		if ( ! question || ! Array.isArray( question.options ) ) {
			return null;
		}

		const selectedIndex = Number.parseInt( quiz.feedback.selected_index ?? -1, 10 );
		const correctIndex = Number.parseInt( quiz.feedback.correct_index ?? -1, 10 );

		return {
			question_index: questionIndex,
			total: Number.parseInt( quiz.total || question.options.length || 0, 10 ),
			question: question.question || '',
			options: question.options,
			selected_index: selectedIndex,
			correct_index: correctIndex,
			is_correct: !! quiz.feedback.is_correct,
			explanation: quiz.feedback.explanation || '',
		};
	}

	getQuizReviewKey( review ) {
		return [
			review.question_index,
			review.total,
			review.selected_index,
			review.correct_index,
			review.is_correct ? 1 : 0,
		].join( '|' );
	}

	pushQuizReviewToHistory( review ) {
		const reviewKey = this.getQuizReviewKey( review );
		if ( reviewKey === this.lastQuizReviewSignature ) {
			return false;
		}
		this.lastQuizReviewSignature = reviewKey;

		this.history.push( {
			type: 'quiz_review',
			review,
		} );
		this.saveHistory();

		return true;
	}

	/**
	 * Build one quiz option button.
	 *
	 * @param {string}   option       Option text from model output.
	 * @param {number}   index        Zero-based option index.
	 * @param {string[]} extraClasses Additional state classes.
	 * @return {HTMLButtonElement} The option button.
	 */
	buildQuizOption( option, index, extraClasses = [] ) {
		const classes = [ 'lp-ai-assistant__quiz-option', ...extraClasses ].join( ' ' );
		const letter = String.fromCharCode( 65 + index );

		const btn = this.createEl( 'button', classes, `${ letter }. ${ String( option ) }` );
		btn.type = 'button';

		return btn;
	}

	buildQuizReviewOptions( review ) {
		const options = Array.isArray( review.options ) ? review.options : [];

		return options.map( ( option, index ) => {
			const classes = [];
			if ( index === review.correct_index ) {
				classes.push( 'is-correct-answer' );
			}

			if ( index === review.selected_index ) {
				classes.push( review.is_correct ? 'is-selected-correct' : 'is-selected-wrong' );
			}

			const btn = this.buildQuizOption( option, index, classes );
			btn.disabled = true;

			return btn;
		} );
	}

	appendQuizReviewCard( review ) {
		const card = this.createEl( 'div', 'lp-ai-assistant__quiz-card lp-ai-assistant__quiz-card--review' );
		card.dataset.reviewKey = this.getQuizReviewKey( review );

		const optionCount = Array.isArray( review.options ) ? review.options.length : 0;
		const total = review.total || optionCount;

		card.appendChild(
			this.createEl( 'div', 'lp-ai-assistant__quiz-head', `Question ${ review.question_index + 1 }/${ total }` )
		);
		card.appendChild( this.createEl( 'div', 'lp-ai-assistant__quiz-question', review.question || '' ) );

		const optionsEl = this.createEl( 'div', 'lp-ai-assistant__quiz-options' );
		this.buildQuizReviewOptions( review ).forEach( ( btn ) => optionsEl.appendChild( btn ) );
		card.appendChild( optionsEl );

		const feedbackClass = review.is_correct ? 'is-correct' : 'is-wrong';
		const feedbackEl = this.createEl( 'div', `lp-ai-assistant__quiz-feedback ${ feedbackClass }` );
		feedbackEl.appendChild(
			this.createEl(
				'strong',
				'',
				review.is_correct ? this.config.i18n.quizCorrectTitle : this.config.i18n.quizWrongTitle
			)
		);

		if ( review.explanation ) {
			feedbackEl.appendChild( this.createEl( 'div', '', review.explanation ) );
		}

		card.appendChild( feedbackEl );

		this.elements.msgList.appendChild( card );
	}

	renderQuizState() {
		const oldActiveQuizCard = this.elements.msgList.querySelector( '.lp-ai-assistant__quiz-card--active' );
		if ( oldActiveQuizCard ) {
			oldActiveQuizCard.remove();
		}

		if ( ! this.activeQuizState || ! this.activeQuizState.questions ) {
			this.setQuizInputMode( false );
			return;
		}

		const quiz = this.activeQuizState;
		const review = this.getQuizReviewFromState( quiz );
		if ( review ) {
			if ( this.pushQuizReviewToHistory( review ) ) {
				this.appendQuizReviewCard( review );
			}
		}

		if ( ! quiz.is_active ) {
			this.setQuizInputMode( false );
			return;
		}

		const currentIndex = Number.parseInt( quiz.current_index || 0, 10 );
		const question = quiz.questions?.[ currentIndex ];
		if ( ! question ) {
			this.setQuizInputMode( false );
			return;
		}

		const card = this.createEl( 'div', 'lp-ai-assistant__quiz-card lp-ai-assistant__quiz-card--active' );
		const options = Array.isArray( question.options ) ? question.options : [];

		card.appendChild(
			this.createEl(
				'div',
				'lp-ai-assistant__quiz-head',
				`Question ${ currentIndex + 1 }/${ quiz.total || options.length }`
			)
		);
		card.appendChild( this.createEl( 'div', 'lp-ai-assistant__quiz-question', question.question || '' ) );

		const optionsEl = this.createEl( 'div', 'lp-ai-assistant__quiz-options' );
		options.forEach( ( option, index ) => {
			const btn = this.buildQuizOption( option, index );

			// Assigned through dataset, never interpolated into an HTML attribute — model
			// output containing a quote cannot open a new attribute or event handler.
			btn.dataset.index = String( index );
			btn.dataset.option = String( option );

			optionsEl.appendChild( btn );
		} );
		card.appendChild( optionsEl );

		this.elements.msgList.appendChild( card );
		this.setQuizInputMode( true );
	}

	scrollToMessageStart( messageEl ) {
		const msgList = this.elements.msgList;
		if ( ! msgList || ! messageEl || ! msgList.contains( messageEl ) ) {
			return;
		}

		msgList.scrollTop = Math.max( 0, messageEl.offsetTop - 8 );
	}

	sendMessage( message, actionHint = '' ) {
		const text = ( message || '' ).trim();
		if ( this.isRequesting || ! text ) {
			return;
		}

		const ajaxHandle = this.getAjaxHandle();
		if ( ! ajaxHandle ) {
			this.appendMessage( 'assistant', this.config.i18n.sendError );
			return;
		}

		this.appendMessage( 'user', text );

		const contextHistory = this.history.slice();
		this.history.push( { role: 'user', content: text } );
		this.saveHistory();
		if ( this.elements.inputEl ) {
			this.elements.inputEl.value = '';
		}

		const pendingEl = this.appendMessage( 'assistant', this.config.i18n.thinking );
		const pendingTextEl = pendingEl.querySelector( '.lp-ai-assistant__msg-text' );
		this.setLoadingState( true );

		const dataSend = {
			action: 'openai_assistant_chat',
			message: text,
			// Composite item identity — all three legs are required server-side.
			course_id: this.config.courseId,
			item_type: this.config.itemType,
			item_id: this.config.itemId,
			history: contextHistory,
			active_quiz_questions: this.activeQuizState || [],
			action_hint: typeof actionHint === 'string' ? actionHint : '',
		};

		const callBack = {
			success: ( response ) => {
				if ( response?.status === 'success' && response?.data ) {
					pendingTextEl.textContent = response.data.message || '';

					if ( response?.data?.type === 'quiz' ) {
						this.activeQuizState = response?.data?.quiz || null;
						this.renderQuizState();

						const isQuizCompleted = !! this.activeQuizState?.completed || this.activeQuizState?.is_active === false;
						if ( isQuizCompleted && this.elements.msgList?.contains( pendingEl ) ) {
							// Keep completion feedback after the last review card.
							this.elements.msgList.appendChild( pendingEl );
						}
					} else {
						this.activeQuizState = null;
						this.renderQuizState();
					}

					this.history.push( { role: 'assistant', content: response.data.message } );
					this.saveHistory();
				} else {
					this.activeQuizState = null;
					this.renderQuizState();
					pendingTextEl.textContent = response?.message || this.config.i18n.sendError;
				}
			},
			error: () => {
				this.activeQuizState = null;
				this.renderQuizState();
				pendingTextEl.textContent = this.config.i18n.sendError;
			},
			completed: () => {
				this.setLoadingState( false );
				this.scrollToMessageStart( pendingEl );
			},
		};

		ajaxHandle.fetchAJAX( dataSend, callBack );
	}
}

const aiAssistantWidget = new AIAssistantWidget();
lpUtils.lpOnElementReady( AIAssistantWidget.selectors.root, () => {
	aiAssistantWidget.init();
} );

```
