/* global WPFormsAIChatHTMLElement, WPFormsBuilder, wpf, wpforms_builder */
/**
* The WPForms AI chat element.
*
* Choices helpers module.
*
* @since 1.9.1
*
* @param {WPFormsAIChatHTMLElement} chat The chat element.
*
* @return {Object} The choices' helpers object.
*/
export default function( chat ) { // eslint-disable-line max-lines-per-function
/**
* The `choices` mode helpers object.
*
* @since 1.9.1
*/
return {
/**
* Get the `choices` answer based on AI response data.
*
* @since 1.9.1
*
* @param {Object} response The response data.
*
* @return {string} Answer HTML markup.
*/
getAnswer( response ) {
if ( response.choices?.length < 1 ) {
return '';
}
const li = [];
for ( const i in response.choices ) {
li.push( `
${ chat.htmlSpecialChars( response.choices[ i ] ) }
` );
}
let answerHtml = `
${ chat.htmlSpecialChars( response.heading ?? '' ) }
${ li.join( '' ) }
`;
// Add footer to the first answer only.
if ( ! chat.sessionId ) {
answerHtml += `${ chat.modeStrings.footer }`;
}
return answerHtml;
},
/**
* Get the answer pre-buttons HTML markup.
*
* @since 1.9.1
*
* @return {string} The answer pre-buttons HTML markup.
*/
getAnswerButtonsPre() {
return `
`;
},
/**
* Get the warning message HTML markup.
*
* @since 1.9.1
*
* @return {string} The warning message HTML markup.
*/
getWarningMessage() {
// Trigger event before warning message insert.
chat.triggerEvent( 'wpformsAIModalBeforeWarningMessageInsert', { fieldId: chat.fieldId } );
return `
`;
},
/**
* If the field has default choices, the welcome screen is active.
*
* @since 1.9.1
*
* @return {boolean} True if the field has default choices, false otherwise.
*/
isWelcomeScreen() {
const items = document.getElementById( `wpforms-field-option-row-${ chat.fieldId }-choices` )
.querySelectorAll( 'li input.label' );
if ( items.length === 1 && ! items[ 0 ].value.trim() ) {
return true;
}
if ( items.length > 3 ) {
return false;
}
const defaults = Object.values( chat.modeStrings.defaults );
for ( let i = 0; i < items.length; i++ ) {
if ( ! defaults.includes( items[ i ].value ) ) {
return false;
}
}
return true;
},
/**
* Add the `choices` answer.
*
* @since 1.9.1
*
* @param {HTMLElement} element The answer element.
*/
addedAnswer( element ) {
const button = element.querySelector( '.wpforms-ai-chat-choices-insert' );
// Listen to the button click event.
button?.addEventListener( 'click', this.insertButtonClick.bind( this ) );
},
/**
* Sanitize response.
*
* @since 1.9.2
*
* @param {Object} response The response data to sanitize.
*
* @return {Object} The sanitized response.
*/
sanitizeResponse( response ) {
if ( ! Array.isArray( response?.choices ) ) {
return response;
}
let choices = response.choices;
// Sanitize choices.
choices = choices.map( ( choice ) => {
return wpf.sanitizeHTML( choice, wpforms_builder.allowed_label_html_tags );
} );
// Remove empty choices.
response.choices = choices.filter( ( choice ) => {
return choice.trim() !== '';
} );
return response;
},
/**
* Check if the response has a prohibited code.
*
* @since 1.9.2
*
* @param {Object} response The response data.
* @param {Array} sanitizedResponse The sanitized response data.
*
* @return {boolean} Whether the answer has a prohibited code.
*/
hasProhibitedCode( response, sanitizedResponse ) {
// If the number of choices has changed after sanitization, it means that the answer contains prohibited code.
return sanitizedResponse?.choices?.length !== response?.choices?.length;
},
/**
* Click on the Use Choices button.
*
* @since 1.9.1
*
* @param {Event} e The event object.
*/
insertButtonClick( e ) {
const button = e.target;
const answer = button.closest( '.wpforms-chat-item.wpforms-chat-item-choices' );
const responseId = answer?.getAttribute( 'data-response-id' );
const choicesList = answer?.querySelector( 'ol' );
const items = choicesList.querySelectorAll( '.wpforms-ai-chat-choices-item' );
const choiceItems = [];
// Get choices data.
for ( const i in items ) {
if ( ! items.hasOwnProperty( i ) || ! items[ i ].textContent ) {
continue;
}
choiceItems.push( items[ i ].textContent.trim() );
}
// Rate the response.
chat.wpformsAiApi.rate( true, responseId );
// Replace field choices.
this.replaceChoices( choiceItems );
},
/**
* Replace field choices.
*
* @since 1.9.1
*
* @param {Array} choices Choices array.
*/
replaceChoices( choices ) {
const choicesOptionRow = document.getElementById( `wpforms-field-option-row-${ chat.fieldId }-choices` );
const choicesList = choicesOptionRow.querySelector( 'ul.choices-list' );
const choiceRow = choicesList.querySelector( 'li:first-child' ).cloneNode( true );
choiceRow.innerHTML = choiceRow.innerHTML.replace( /\[choices\]\[\d+\]/g, `[choices][{{key}}]` );
// Clear existing choices.
choicesList.innerHTML = '';
// Add new choices.
for ( const i in choices ) {
const key = ( Number( i ) + 1 ).toString();
const choice = choices[ i ];
// Clone choice item element.
let li = choiceRow.cloneNode( true );
// Get updated single choice item.
li = this.getUpdatedSingleChoiceItem( li, key, choice );
// Add new choice item.
choicesList.appendChild( li );
}
// Update data-next-id attribute for choices list.
choicesList.setAttribute( 'data-next-id', choices.length + 1 );
// Update field preview.
const fieldOptions = document.getElementById( `wpforms-field-option-${ chat.fieldId }` );
const fieldType = fieldOptions.querySelector( 'input.wpforms-field-option-hidden-type' )?.value;
WPFormsBuilder.fieldChoiceUpdate( fieldType, chat.fieldId, choices.length );
WPFormsBuilder.triggerBuilderEvent( 'wpformsFieldChoiceAdd' );
// Trigger event after choices insert.
chat.triggerEvent( 'wpformsAIModalAfterChoicesInsert', { fieldId: chat.fieldId } );
},
/**
* Get updated single choice item.
*
* @since 1.9.1
*
* @param {HTMLElement} li Choice item element.
* @param {string} key Choice key.
* @param {string} choice Choice value.
*
* @return {HTMLElement} The updated choice item.
*/
getUpdatedSingleChoiceItem( li, key, choice ) {
li.setAttribute( 'data-key', key.toString() );
// Update choice item inputs name attributes.
li.innerHTML = li.innerHTML.replaceAll( '{{key}}', key );
// Sanitize choice before set.
choice = wpf.sanitizeHTML( choice );
const inputDefault = li.querySelector( 'input.default' );
inputDefault.removeAttribute( 'checked' );
// Set label
const inputLabel = li.querySelector( 'input.label' );
inputLabel.value = choice;
inputLabel.setAttribute( 'value', choice );
// Set value.
const inputValue = li.querySelector( 'input.value' );
inputValue.value = choice;
inputValue.setAttribute( 'value', choice );
// Reset image upload.
const imageUpload = li.querySelector( '.wpforms-image-upload' );
const inputImage = imageUpload.querySelector( 'input.source' );
inputImage.value = '';
inputImage.setAttribute( 'value', '' );
imageUpload.querySelector( '.preview' ).innerHTML = '';
imageUpload.querySelector( '.wpforms-image-upload-add' ).style.display = 'block';
// Reset icon choice.
const iconSelect = li.querySelector( '.wpforms-icon-select' );
iconSelect.querySelector( '.ic-fa-preview' ).setAttribute( 'class', 'ic-fa-preview ic-fa-regular ic-fa-face-smile' );
iconSelect.querySelector( 'input.source-icon' ).value = 'face-smile';
iconSelect.querySelector( 'input.source-icon-style' ).value = 'regular';
return li;
},
};
}