/* global beyondwordsData */ /* * Classic-editor Customize + Language + Accent + Model + Voice behaviour. Mirrors * the block editor's voice-section.js + helpers.js. Language lists names while * Accent carries the submitted language CODE. The selects live in the post *
and submit on save, so no autosave/Heartbeat hook is needed. */ ( function () { 'use strict'; const data = typeof beyondwordsData !== 'undefined' ? beyondwordsData : null; const LANGUAGES = ( data && data.languages ) || []; const ELEVENLABS = ( data && data.elevenLabs ) || 'ElevenLabs'; const DEFAULT_MODEL_ID = ( data && data.defaultModelId ) || 'eleven_multilingual_v2'; const SELECT_VOICE = ( data && data.selectVoice ) || 'Select a voice'; const SELECT_MODEL = ( data && data.selectModel ) || 'Select a model'; const STANDARD_MODEL = ( data && data.standardModel ) || 'Legacy'; const MODEL_LABELS = ( data && data.voiceModelLabels ) || {}; // Bucket key for voices without an ElevenLabs model_id (e.g. standard voices). const STANDARD_MODEL_KEY = 'standard'; /** * Human label for a model_id slug. * * @param {string} modelId The model_id slug. * * @return {string} A display label. */ const modelLabel = ( modelId ) => { if ( MODEL_LABELS[ modelId ] ) { return MODEL_LABELS[ modelId ]; } return String( modelId ) .replace( /^eleven_/, '' ) .replace( /_/g, ' ' ) .replace( /\b\w/g, ( c ) => c.toUpperCase() ); }; /** * The model bucket key for a voice: its ElevenLabs model_id, else Standard. * * @param {Object} voice A voice record. * * @return {string} The model bucket key. */ const voiceModelKey = ( voice ) => { if ( voice && voice.service === ELEVENLABS && typeof voice.model_id === 'string' ) { return voice.model_id; } return STANDARD_MODEL_KEY; }; /** * A voice's primary (native) language code. * * @param {Object} voice A voice record. * * @return {string} The primary language code, or ''. */ const voicePrimaryCode = ( voice ) => { const language = voice && voice.language; if ( typeof language === 'string' ) { return language; } if ( language && typeof language === 'object' && language.code ) { return language.code; } return ( ( voice && voice.languages && voice.languages[ 0 ] ? voice.languages[ 0 ].code : '' ) || '' ); }; /** * Whether a voice is native to a language code. A voice with no * determinable primary language is treated as native, so it is never hidden. * * @param {Object} voice A voice record. * @param {string} code The language code. * * @return {boolean} Whether the voice is native to the code. */ const voiceIsNative = ( voice, code ) => { const primary = voicePrimaryCode( voice ); if ( ! primary ) { return true; } return String( primary ) === String( code ); }; /** * The distinct model buckets across a language's voices, for the Model dropdown. * * @param {Array} voices All voices for the current language. * * @return {Array<{key: string, label: string}>} The Model dropdown options. */ const languageModels = ( voices ) => { const modelIds = []; let hasStandard = false; ( voices || [] ).forEach( ( voice ) => { const key = voiceModelKey( voice ); if ( key === STANDARD_MODEL_KEY ) { hasStandard = true; } else if ( ! modelIds.includes( key ) ) { modelIds.push( key ); } } ); modelIds.sort( ( a, b ) => { if ( a === DEFAULT_MODEL_ID ) { return -1; } if ( b === DEFAULT_MODEL_ID ) { return 1; } return 0; } ); const models = modelIds.map( ( key ) => ( { key, label: modelLabel( key ), } ) ); if ( hasStandard ) { models.push( { key: STANDARD_MODEL_KEY, label: STANDARD_MODEL } ); } return models; }; const option = ( value, text ) => { const el = document.createElement( 'option' ); el.value = value; el.textContent = text; return el; }; const byId = ( id ) => document.getElementById( id ); /** * The language rows (accents) for a language name, in API order. * * @param {string} name The language name, or ''. * * @return {Array} The matching slim language rows. */ const accentsForName = ( name ) => name ? LANGUAGES.filter( ( language ) => language.name === name ) : []; /** * Find a slim language row by its code. * * @param {string} code The language code. * * @return {Object|null} The matching row, or null. */ const findLanguageByCode = ( code ) => LANGUAGES.find( ( language ) => String( language.code ) === String( code ) ) || null; /** * An Accent