PageSourceSearch

https://widget-dev.lovi.ai/iawidget.js

js lovi.ai collected 2026-10-05 16:26:23 UTC 36,473 bytes, 763 lines download raw bytes

1(function () {
2    // ======== CONFIG DE ENTORNO ========
3    // Único lugar a cambiar al pasar de dev a producción.
4    const WIDGET_URL = 'https://widget-dev.lovi.ai';
5    const FUNCTIONS_BASE = 'https://vditxnjbejitzkcfqzzv.supabase.co/functions/v1';
6    // ======== FIN CONFIG DE ENTORNO ========
7
8    //Create elemento ia-widget
9    const node_element = document.createElement("app-chatbot");
10    node_element.setAttribute("id", "app-chatbot");
11    document.body.appendChild(node_element);
12
13    //Conditional wp
14    const mobile = (window.innerWidth <= 650) ? 'on' : 'off';
15    const whatsapp_type = document.currentScript.getAttribute("whatsapp_type") ? document.currentScript.getAttribute("whatsapp_type") : false;
16    const whatsapp_number = document.currentScript.getAttribute("whatsapp_number") ? document.currentScript.getAttribute("whatsapp_number") : false;
17    // mode="voice" → carga el widget de voz (ElevenLabs) en vez del chat de texto.
18    const mode = document.currentScript.getAttribute("mode") || false;
19    const voice_customer = document.currentScript.getAttribute("customer") || false;
20    const voice_id_widget = document.currentScript.getAttribute("widget") || false;
21    const voice_language_attr = document.currentScript.getAttribute("language-lovi") || false;
22
23    // Carga el widget de VOZ: iframe transparente con ?mode=voice y responde el
24    // handshake get_voice (main.js) con identidad + config para VoiceWidget.vue.
25    const load_voice_widget = async () => {
26        if (!voice_customer || !voice_id_widget) {
27            console.error("LOVI ERROR (voz): customer/widget no definidos.");
28            return false;
29        }
30        const url = WIDGET_URL;
31        const functionsBase = FUNCTIONS_BASE;
32
33        // Identidad web: misma llave/criterio que el widget de texto (ia_infouser
34        // bajo la clave id_widget). El `id` se inyecta como user_id → el post-call
35        // crea el chat (mismo rastro que Ringover).
36        const STORAGE_KEY = voice_id_widget;
37        const readStore = () => { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; } catch { return {}; } };
38        let info_user = readStore().ia_infouser;
39        if (!info_user || !info_user.id) {
40            const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
41            info_user = { id };
42            localStorage.setItem(STORAGE_KEY, JSON.stringify({ ...readStore(), ia_infouser: info_user }));
43        }
44
45        let language = voice_language_attr || (navigator.language || navigator.userLanguage || 'en').slice(0, 2);
46        if (language === 'un') language = 'en';
47
48        // Apariencia del orbe (color/posición/animación) configurada por empresa en
49        // channel_accounts.metadata['Voice-config']. Defaults si falla.
50        let voiceConfig = {};
51        try {
52            const res = await fetch(`${functionsBase}/voice-widget?access_key=${encodeURIComponent(voice_customer)}&id_widget=${encodeURIComponent(voice_id_widget)}&language=${encodeURIComponent(language)}`);
53            const data = await res.json();
54            if (!data?.error) voiceConfig = data?.widget?.["Voice-config"] || {};
55        } catch (e) { /* defaults */ }
56
57        // El handshake debe estar escuchando ANTES de que el iframe cargue y lo pida.
58        window.addEventListener("message", function (e) {
59            if (e.data?.tipo_peticion === "get_voice") {
60                const iframe = document.getElementById("app-chatbot-voice-iframe");
61                iframe?.contentWindow?.postMessage({
62                    tipo: "get_voice",
63                    access_key: voice_customer,
64                    id_widget: voice_id_widget,
65                    language,
66                    voiceConfig,
67                    functionsBase,
68                    info_user,
69                }, "*");
70            } else if (e.data?.tipo_peticion === "voice_resize") {
71                // El VoiceWidget avisa su tamaño real (cápsula sola o con transcript).
72                // Redimensionamos el iframe para que no recorte el contenido; vuelve
73                // a achicarse a la cápsula al cerrar (mantiene el rectángulo mínimo
74                // que no bloquea clics del resto de la página).
75                const vf = document.getElementById("app-chatbot-voice-iframe");
76                if (vf && e.data.width && e.data.height) {
77                    vf.style.width = e.data.width + "px";
78                    vf.style.height = e.data.height + "px";
79                }
80            }
81        }, false);
82
83        // Iframe transparente pequeño: su rectángulo SÍ captura clics, por eso se
84        // mantiene ajustado a la cápsula (ver decisión previa). Posición según config.
85        const side = voiceConfig.floating_position === "left" ? "left:0;" : "right:0;";
86        const iframe = document.createElement("iframe");
87        iframe.id = "app-chatbot-voice-iframe";
88        iframe.src = `${url}?mode=voice`;
89        iframe.allow = "microphone; autoplay";
90        iframe.setAttribute("frameBorder", "0");
91        iframe.style.cssText = `position:fixed;bottom:0;${side}width:285px;height:104px;border:0;background:transparent;z-index:99999999999;`;
92        document.querySelector("#app-chatbot").appendChild(iframe);
93    };
94
95    //Function load whatsapp
96    const load_whatsapp = (whatsapp_number) => {
97        const iframe_element = `
98        <a href="https://wa.me/${whatsapp_number}" target="_blank" class="widget_chat_switch_wp">
99           <svg width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-brand-whatsapp"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9" /><path d="M9 10a.5 .5 0 0 0 1 0v-1a.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a.5 .5 0 0 0 0 -1h-1a.5 .5 0 0 0 0 1" /></svg>
100        </a>
101        <style>.widget_chat_switch_wp{position:fixed;bottom:10px;right:20px;cursor:pointer;display:flex;align-items:center;width:50px;height:50px;border-radius:50%;background:#27d367;justify-content:center;box-shadow:rgba(0,18,46,.18) 0 2px 20px 0;color:#27d367;animation:2s infinite pulse;z-index:99999999999999999999999}.widget_chat_switch_wp svg{width:40px;height:40px;color:#fff}@keyframes pulse{0%{-webkit-box-shadow:0 0 #27d367;box-shadow:0 0 #27d367}80%{-webkit-box-shadow:0 0 7px 15px rgba(238,105,128,0);box-shadow:0 0 7px 15px rgba(238,105,128,0)}100%{-webkit-box-shadow:0 0 0 0 rgba(238,105,128,0);box-shadow:0 0 0 0 rgba(238,105,128,0)}}</style>`;
102        document.querySelector("#app-chatbot").insertAdjacentHTML("afterbegin", iframe_element);
103    }
104
105    //Function load_ia_widget
106    const load_ia_widget = () => {
107        let widget = false;
108        let language = "en";
109        let company = false;
110        let name_company = false;
111        let previewBusy = false;
112        let url = WIDGET_URL;
113        const messageQueue = [];
114        const customer = document.currentScript.getAttribute("customer") ? document.currentScript.getAttribute("customer") : false;
115        const id_widget = document.currentScript.getAttribute("widget") ? document.currentScript.getAttribute("widget") : false;
116        const dom = document.currentScript.getAttribute("dom") ? document.currentScript.getAttribute("dom") : false;
117        const event = document.currentScript.getAttribute("event") ? document.currentScript.getAttribute("event") : false;
118        const contexto_extra = document.currentScript.getAttribute("extra-context") ? document.currentScript.getAttribute("extra-context").split(",") : [];
119        const showClose = document.currentScript.getAttribute("showClose") !== 'false';
120        const languageLovi = document.currentScript.getAttribute("language-lovi") || false;
121        const source = document.currentScript.getAttribute("lovi-source") ? document.currentScript.getAttribute("lovi-source") : false;
122
123        const params = [];
124        if (!showClose) params.push('showClose=false');
125        if (languageLovi) params.push(`lang=${languageLovi}`);
126        if (params.length > 0) url += `?${params.join('&')}`;
127
128        //Validamos los campos necesarios
129        if (!customer || customer == '') {
130            console.error("LOVI ERROR: The customer of the company ID has not been defined.");
131            return false;
132        }
133        if (!id_widget || id_widget == '') {
134            console.error("LOVI ERROR: The widget ID has not been defined.");
135            return false;
136        }
137
138        // ======== STORAGE HELPERS (Single key = id_widget) ========
139        const STORAGE_KEY = id_widget;
140        const readStore = () => {
141            try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; }
142            catch { return {}; }
143        };
144        const writeStore = (obj) => localStorage.setItem(STORAGE_KEY, JSON.stringify(obj));
145        const setStore = (patch) => { const s = readStore(); writeStore({ ...s, ...patch }); };
146        const getFromStore = (key) => readStore()[key];
147        const removeFromStore = (key) => { const s = readStore(); if (key in s) { delete s[key]; writeStore(s); } };
148
149        // Migración de llaves antiguas (se ejecuta una sola vez)
150        (function migrateLegacyStorage() {
151            const legacyKeys = [
152                'ia_infouser', 'ia_widget', 'ia_company', 'ia_name_company',
153                'ia_open', 'ia_widget_timestamp', 'widget_chat_switch_message'
154            ];
155            const s = readStore();
156            let changed = false;
157            legacyKeys.forEach(k => {
158                const raw = localStorage.getItem(k);
159                if (raw !== null && !(k in s)) {
160                    let val = raw;
161                    try { val = JSON.parse(raw); } catch {
162                        if (raw === 'true') val = true;
163                        else if (raw === 'false') val = false;
164                    }
165                    s[k] = val;
166                    localStorage.removeItem(k);
167                    changed = true;
168                }
169            });
170            if (changed) writeStore(s);
171        })();
172        // ======== FIN STORAGE HELPERS ========
173
174        // Lee una cookie por nombre (solo cookies NO HttpOnly son accesibles desde JS)
175        function getCookie(name) {
176            const match = document.cookie.match(new RegExp('(^|;\\s*)' + name + '=([^;]*)'));
177            return match ? decodeURIComponent(match[2]) : null;
178        }
179
180        //Activar oyente del widget
181        function listener_event_widget() {
182            // Evita duplicados del listener global
183            if (window.__loviWidgetClicksBound) return;
184            window.__loviWidgetClicksBound = true;
185
186            document.addEventListener('click', (e) => {
187                // abrir desde el botón flotante
188                if (e.target.closest('.widget_chat_switch_open')) {
189                    open_widget();
190                    return;
191                }
192                // abrir desde el bocadillo
193                if (e.target.closest('.widget_chat_switch_message p')) {
194                    open_widget();
195                    return;
196                }
197                // cerrar / ocultar bocadillo
198                if (e.target.closest('.widget_chat_switch_message span')) {
199                    const box = document.querySelector('.widget_chat_switch_message');
200                    if (box) box.style.display = 'none';
201                    setStore({ widget_chat_switch_message: true });
202                    return;
203                }
204            }, false);
205        }
206
207        //Activar oyente del servidor
208        function listener_event_server() {
209            // --- define send_iframe (igual que antes), pero más seguro ---
210            const send_iframe = function (tipo) {
211                const iframeEl = document.getElementById("app-chatbot-iframe");
212                const win = iframeEl?.contentWindow;
213                if (!win || !win.postMessage) return;
214
215                if (tipo === "get_customer") {
216                    const info_user = getFromStore("ia_infouser") || null;
217                    win.postMessage({
218                        tipo: "get_customer",
219                        language,
220                        event,
221                        contexto_extra,
222                        mobile,
223                        info_user,
224                        showClose,
225                        source,
226                        access_key: customer,
227                        id_widget
228                    }, "*");
229                }
230
231                if (tipo === "get_widget") {
232                    const ia_widget = getFromStore("ia_widget") || null;
233                    win.postMessage({
234                        tipo: "get_widget",
235                        company,
236                        name_company,
237                        widget: ia_widget
238                    }, "*");
239                }
240
241                if (tipo === "reset_widget") {
242                    win.postMessage({ tipo: "reset_widget" }, "*");
243                }
244
245                if (tipo === "data_collection") {
246                    const payload = {
247                        tipo: "data_collection",
248                        data_collection: { url: window.location.href }
249                    };
250                    if (widget?.local_storage_keys?.length) {
251                        widget.local_storage_keys.forEach(({ key, type, source, name }) => {
252                            let raw;
253                            if (source === "js_global") {
254                                // Lee una variable JS global de la página anfitriona por
255                                // ruta con puntos (p.ej. "drupalSettings.global_datalayer.sf_id").
256                                // Solo el loader puede leerla: corre en la página del cliente,
257                                // no en el iframe cross-origin del widget.
258                                raw = key.split(".").reduce((o, k) => (o == null ? o : o[k]), window);
259                            } else {
260                                raw = localStorage.getItem(key) ?? sessionStorage.getItem(key) ?? getCookie(key);
261                            }
262                            let parsedValue;
263                            try { parsedValue = JSON.parse(raw); } catch { parsedValue = raw; }
264                            // `name` opcional: nombre de salida limpio (p.ej. "sf_id") en vez
265                            // de la ruta larga. Sin `name`, se mantiene `key` (compat).
266                            payload.data_collection[name || key] = { value: parsedValue, type };
267                        });
268                    }
269                    win.postMessage(payload, "*");
270                }
271            };
272
273            // --- evita duplicados: quita el handler previo si existe ---
274            if (window.__loviMsgHandler) {
275                window.removeEventListener("message", window.__loviMsgHandler);
276            }
277
278            const handler = function (e) {
279                const data = e.data;
280                if (data?.tipo_peticion === "get_widget") {
281                    send_iframe("get_widget");
282                } else if (data?.tipo_peticion === "get_customer") {
283                    send_iframe("get_customer");
284                } else if (data?.tipo_peticion === "data_collection") {
285                    send_iframe("data_collection");
286                } else if (data?.tipo_peticion === "save_customer") {
287                    setStore({ ia_infouser: data.info_user });
288                } else if (data?.tipo_peticion === "close") {
289                    if (showClose) {
290                        if (mobile === "on") document.body.style.overflow = "auto";
291                        const iframe = document.querySelector("#app-chatbot-iframe");
292                        if (iframe?.style) iframe.style.display = "none";
293                        const openBtn = document.querySelector(".widget_chat_switch_open");
294                        if (openBtn?.style) openBtn.style.display = "block";
295                        const bubble = document.querySelector(".widget_chat_switch_message");
296                        if (bubble) bubble.style.display = "flex";
297                        setStore({ ia_open: false });
298                    }
299                }
299 else if (data?.tipo_peticion === "reset") {
300                    removeFromStore("ia_infouser");
301                    create_id_unique();
302                    send_iframe("reset_widget");
303                } else if (data?.tipo_peticion === "new_message") {
304                    const isOpen = !!getFromStore('ia_open');
305                    if (isOpen) return;
306                    const alertDot = document.querySelector(".widget_chat_switch_open_alert");
307                    if (alertDot?.style) alertDot.style.display = "block";
308                    if (document.querySelector(".widget_chat_switch_message_preview") && data.message?.text) {
309                        const msg = data.message.text;
310                        if (msg) { messageQueue.push(msg); processQueue(); }
311                    }
312                }
313            };
314
315            window.__loviMsgHandler = handler;
316            window.addEventListener("message", handler, false);
317        }
318
319        //Validamos el idioma
320        (function validate_idioma() {
321            if (languageLovi) {
322                language = languageLovi;
323            } else {
324                language = navigator.language || navigator.userLanguage;
325                language = language.slice(0, 2);
326                if (language == 'un') {
327                    language = 'en';
328                }
329            }
330        })();
331
332        //Creamos el id_unique
333        function create_id_unique() {
334            let info = getFromStore("ia_infouser");
335            if (!info || !info.id) {
336                const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
337                setStore({ ia_infouser: { id } });
338            }
339        }
340        create_id_unique();
341
342        //Detectamos el ancho del texto
343        function getTextWidth(text, font) {
344            var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
345            var context = canvas.getContext("2d");
346            context.font = font;
347            var metrics = context.measureText(text);
348            return metrics.width;
349        };
350
351        // Cola y estado de preview
352        function processQueue() {
353            if (previewBusy || messageQueue.length === 0) return;
354            const message = messageQueue.shift();
355            displayPreview(message);
356        }
357        function displayPreview(message) {
358            const preview = document.querySelector(".widget_chat_switch_message_preview");
359            preview.innerHTML = `<p>${message}</p>`;
360            preview.style.display = "block";
361            preview.classList.remove("animate-hide");
362            void preview.offsetWidth;
363            preview.classList.add("animate-show");
364            const paddingTotal = 20;
365            const fontSpec = "normal 14px sans-serif";
366            const textWidth = getTextWidth(message, fontSpec);
367            preview.style.width = Math.min(textWidth + paddingTotal, 220) + "px";
368            previewBusy = true;
369            setTimeout(() => {
370                preview.classList.remove("animate-show");
371                preview.classList.add("animate-hide");
372                setTimeout(() => {
373                    preview.style.display = "none";
374                    previewBusy = false;
375                    processQueue();
376                }, 500);
377            }, 5000);
378        }
379
380        //funcion para abrir el widget
381        function open_widget() {
382            setStore({ ia_open: true });
383            // Ocultar icono y mensaje principal
384            const open_icon = document.querySelector(".widget_chat_switch_open");
385            if (open_icon) open_icon.style.display = "none";
386            const widget_chat_switch_message = document.querySelector(".widget_chat_switch_message");
387            if (widget_chat_switch_message) widget_chat_switch_message.style.display = "none";
388
389            // Limpiar alerta y preview
390            const newAlert = document.querySelector(".widget_chat_switch_open_alert");
391            if (newAlert) newAlert.style.display = "none";
392            const preview = document.querySelector(".widget_chat_switch_message_preview");
393            if (preview) {
394                preview.style.display = "none";
395                preview.classList.remove("animate-show", "animate-hide");
396                preview.innerHTML = "";
397            }
398
399            // Crear o mostrar el iframe
400            let iframe_element = document.querySelector("#app-chatbot-iframe");
401            if (!iframe_element) {
402                iframe_element = `<iframe id="app-chatbot-iframe" src="${url}" frameBorder="0" allowfullscreen allow="fullscreen; autoplay; encrypted-media; picture-in-picture" style="overflow-y: hidden;display: none;" class="bounce-in-down"></iframe>`;
403                document.querySelector("#app-chatbot").insertAdjacentHTML("afterbegin", iframe_element);
404            }
405            if (mobile == 'on') document.body.style.overflow = "hidden";
406
407            setTimeout(() => {
408                document.querySelector("#app-chatbot-iframe").style.display = "block";
409            }, 500);
410        }
411
412        //Insert button open and close
413        function insertButton() {
414            //Verificamos si showClose es false para mostrar en pantalla completa
415            if (!showClose) {
416                let iframe_element = `<iframe id="app-chatbot-iframe" src="${url}" frameBorder="0" allowfullscreen allow="fullscreen; autoplay; encrypted-media; picture-in-picture" style="width: 100%; height: 100%; position: fixed; top: 0; left: 0; z-index: 99999999999999999999999; display: block;"></iframe>`;
417                document.querySelector("#app-chatbot").insertAdjacentHTML("afterbegin", iframe_element);
418                listener_event_server();
419                if (mobile == 'on') document.body.style.overflow = "hidden";
420                return;
421            }
422
423            //Verificamos si whassap esta activo
424            if (widget?.whatsapp_type == true && widget?.whatsapp_number != '' && mobile == 'on') {
425                let whatsapp_number = widget?.whatsapp_number;
426                load_whatsapp(whatsapp_number);
427                return false;
428            }
429
430            //Verificamos si va dentro de algun elemento del dom
431            if ((widget.is_fullscreen && widget.element_dom && document.getElementById(widget.element_dom)) || dom) {
432                //Insertamos el iframe dentro del elemento dom
433                let dom_element = widget.element_dom ? widget.element_dom : dom;
434                let iframe_element = `<iframe id="app-chatbot-iframe" src="${url}" frameBorder="0" allowfullscreen allow="fullscreen; autoplay; encrypted-media; picture-in-picture" style="overflow-y: hidden;"></iframe>`;
435                iframe_element += `<style>#app-chatbot-iframe{width:100%;height:100%}</style>`;
436                const parentElement = document.getElementById(dom_element);
437                parentElement.replaceChildren();
438                parentElement.insertAdjacentHTML("afterbegin", iframe_element);
439                document.querySelector("#app-chatbot-iframe").style.display = "block";
440                listener_event_server();
441            } else {
442                let message = false;
443                let anchoWidth = 0;
444                const fm = widget?.translations?.[language]?.floating_message;
445                if (typeof fm === 'string' && fm !== '') {
446                    message = fm;
447                    anchoWidth = getTextWidth(fm, "bold 15px sans-serif");
448                    if (anchoWidth > 300) anchoWidth = 300;
449                    const dismissed = !!getFromStore('widget_chat_switch_message');
450                    if (dismissed) message = false;
451                }
452
453                //Inser css style + markup
454                let iframe_element = `
455            <div class="widget_chat_switch">
456                ${(message) ? `
457                <div class="widget_chat_switch_message">
458                    <p>${message}</p>
459                    <span>
460                        <svg width="15" height="15" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
461                            <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
462                            <path d="M18 6l-12 12"></path>
463                            <path d="M6 6l12 12"></path>
464                        </svg>
465                    </span>
466                </div>` : ``}
467                <div class="widget_chat_switch_message_preview"></div>
468                <div class="widget_chat_switch_open">
469                    <img src="${widget.avatar_open}" alt="">
470                    <div class="widget_chat_switch_open_alert"></div>
471                </div>
472            </div>
473            <style>
474            #app-chatbot {
475                position: fixed;
476                bottom: 10px;
477                right: 0;
478                z-index: 99999999999999999999999;
479            }
480            .widget_chat_switch {
481                position: fixed;
482                bottom: 10px;
483                right: 20px;
484                cursor: pointer;
485                display: flex;
486                align-items: center;
487                animation: up-down 7.5s infinite ease-in-out;
488            }
489            .widget_chat_switch img {
490                width: 50px;
491                height: 50px;
492                border-radius: 50%;
493                box-shadow: 0 0 10px 0 #ccc;
494                background: ${widget.color_primary};
495                object-fit: contain;
496            }
497            .widget_chat_switch_open_alert{
498                display: none;
499                background: #fb6944;
500                width: 10px;
501                height: 10px;
502                position: absolute;
503                top: 0;
504                right: 0;
505                border-radius: 50%;
506                border: 2px solid #fff;
507            }
508            .widget_chat_switch_open img{
509                animation: pulse 2s infinite;
510                color: ${widget.color_primary};
511            }
512            .widget_chat_switch_open img:hover {
513                animation: none;
514            }
515            #app-chatbot-iframe {
516                display: none;
517                width: 450px;
518                height: 85vh;
519                max-height: 824px;
520                position: fixed;
521                right: 10px;
522                bottom: 10px;
523                border-radius: 20px;
524                z-index: 1;
525            }
526            .bounce-in-down {
527                animation: fadeIn 0.5s ease-in both;
528            }
529            .widget_chat_switch_message{
530                position: absolute;
531                width: ${anchoWidth}px;
532                right: calc(100% + 10px);
533                box-shadow: rgba(0, 18, 46, 0.18) 0px 2px 20px 0px;
534                border-radius: 50px;
535                padding: 10px 5px 10px 15px;
536                background: ${widget.color_primary};
537                display: flex;
538                align-items: center;
539                box-sizing: content-box;
540            }
541            .widget_chat_switch_message::after{
542                position: absolute;
543                content: "";
544                width: 0;
545                height: 0;
546                border-left: 10px solid ${widget.color_primary};
547                border-top: 5px solid transparent;
548                border-bottom: 5px solid transparent;
549                right: -8px;
550            }
551            .widget_chat_switch_message_preview {
552                display: none;
553                position: absolute;
554                right: 0;
555                bottom: 65px;
556                padding: 10px;
557                background: #fff;
558                border-radius: 10px;
559                box-shadow: 0 0 10px 2px #eee;
560                max-width: 220px;
561                white-space: normal;
562                word-wrap: break-word;
563                opacity: 0;
564                transform: translateY(20px);
565            }
566            .widget_chat_switch_message_preview p {
567                margin: 0;
568                font: normal 14px sans-serif;
569                line-height: 1.5;
570                display: -webkit-box;
571                -webkit-box-orient: vertical;
572                -webkit-line-clamp: 2;
573                overflow: hidden;
574                text-align: center;
575            }
576            .widget_chat_switch_message_preview.animate-show {
577                animation: slideUpInBots 0.4s ease-out forwards;
578            }
579            .widget_chat_switch_message_preview.animate-hide {
580                animation: fadeOutBots 0.5s ease-out forwards;
581            }
582            @keyframes slideUpInBots {
583                0%   { transform: translateY(20px); opacity: 0; }
584                100% { transform: translateY(0);     opacity: 1; }
585            }
586            @keyframes fadeOutBots {
587                0%   { opacity: 1; }
588                100% { opacity: 0; }
589            }
590            .widget_chat_switch_message p{
591                text-align: left;
592                color: ${widget.color_message_text};
593                font: bold 14px sans-serif;
594                margin: 0;
595                white-space: normal;
596                overflow: hidden;
597                display: -webkit-box;
598                -webkit-line-clamp: 2;
599                -webkit-box-orient: vertical;
600                text-overflow: ellipsis;
601            }
602            .widget_chat_switch_message span{
603                position: absolute;
604                right: 5px;
605                top: -10px;
606                width: 20px;
607                height: 20px;
608                border-radius: 50%;
609                background: ${widget.color_primary};
610                align-items: center;
611                justify-content: center;
612                display:none;
613            }
614            .widget_chat_switch_message span svg{
615                color: ${widget.color_message_text};
616            }
617            .widget_chat_switch_message:hover span{
618                display: flex;
619            }
620            @keyframes fadeIn {
621                from { opacity: 0; transform: translate3d(0, 10px, 0); }
622                to   { opacity: 1; transform: translate3d(0, -10px, 0); }
623            }
624            @keyframes pulse {
625                0% { -webkit-box-shadow: 0 0 currentColor; box-shadow: 0 0 currentColor; }
626                80% { -webkit-box-shadow: 0 0 7px 15px rgba(238, 105, 128, 0); box-shadow: 0 0 7px 15px rgba(238, 105, 128, 0); }
627                100% { -webkit-box-shadow: 0 0 0 0 rgba(238, 105, 128, 0); box-shadow: 0 0 0 0 rgba(238, 105, 128, 0); }
628            }
629            @keyframes up-down {
630                0%,25%,50%,75%,100% { transform: translate(0); }
631                12.5%,37.5%,62.5%,87.5% { transform: translate(0, 2%); }
632            }
633            @media (max-height: 650px) {
634                #app-chatbot-iframe {
635                    max-height: 100%;
636                    height:100%;
637                    bottom: 0;
638                    transform: none !important;
639                    top: 0;
640                }
641            }
642            @media (max-width: 650px) {
643                #app-chatbot-iframe {
644                    width: 100%;
645                    height:100%;
646                    left: 0;
647                    right: 0;
648                    max-height: 100%;
649                    bottom: 0;
650                    transform: none !important;
651                    top: 0;
652                    border-radius: 0;
653                }
654            }
655            </style>`;
656                //Verificamos si widget es izquierda
657                if (widget.floating_position == "left") {
658                    iframe_element += `
659                <style>
660                    #app-chatbot{ left: 0; }
661                    .widget_chat_switch{ left: 20px; right: initial; }
662                    .widget_chat_switch_message{ right: initial; left: calc(100% + 10px); }
663                    #app-chatbot-iframe{ right: initial; left: 10px; }
664                    .widget_chat_switch_message::after{
665                        left: -8px;
666                        border-right: 10px solid #013591;
667                        border-left: none;
668                    }
669                </style>`;
670                }
671                document.querySelector("#app-chatbot").insertAdjacentHTML("afterbegin", iframe_element);
672                listener_event_server();
673                listener_event_widget();
674            }
675
676            // Si existe extra_css, inyectarlo en el widget
677            if (widget.extra_css && widget.extra_css.trim().length > 0) {
678                const styleEl = document.createElement("style");
679                styleEl.innerHTML = widget.extra_css;
680                document.querySelector("#app-chatbot").appendChild(styleEl);
681            }
682
683            //Verificamos si ya se abrio el widget
684            const ia_bot_open = !!getFromStore('ia_open');
685            ia_bot_open ? open_widget() : false;
686        }
687
688        // Función para cargar la configuración del widget desde el storage o servidor
689        async function loadWidgetConfig() {
690            const timestampKey = 'ia_widget_timestamp';
691            const oneDay = 24 * 60 * 60 * 1000;
692            const now = Date.now();
693            const storedWidget = getFromStore('ia_widget');
694            const storedTimestamp = getFromStore(timestampKey);
695
696            if (storedWidget && storedTimestamp && (now - parseInt(storedTimestamp, 10)) < oneDay) {
697                widget = storedWidget;
698                company = getFromStore('ia_company') || false;
699                name_company = getFromStore('ia_name_company') || false;
700                insertButton();
701            } else {
702                await get_config_widget();
703                setStore({ [timestampKey]: now.toString() });
704            }
705        }
706
707        // Función para obtener la configuración del widget desde el servidor
708        get_config_widget = async () => {
709            const baseUrl = `${FUNCTIONS_BASE}/widget`;
710            let urlRequest = `${baseUrl}?access_key=${customer}&language=${language}&id_widget=${id_widget}`;
711            const requestOptions = {
712                method: "GET",
713                headers: {
714                    "Content-Type": "application/json",
715                    "Accept": "application/json",
716                },
717                redirect: "follow",
718            };
719            async function fetchData() {
720                try {
721                    const response = await fetch(urlRequest, requestOptions);
722                    if (!response.ok) {
723                        throw new Error(`Error: ${response.status} - ${response.statusText}`);
724                    }
725                    const data = await response.json();
726                    if (data?.company && data?.widget?.translations) {
727                        widget = data.widget;
728                        company = data.company;
729                        name_company = data.name_company;
730                        setStore({
731                            ia_widget: widget,
732                            ia_company: company,
733                            ia_name_company: name_company
734                        });
735                        insertButton();
736                        return data;
737                    } else if (data?.error) {
738                        console.error("LOVI ERROR:", data.error);
739                        return false;
740                    } else {
741                        console.error("LOVI ERROR: Unexpected response structure", data);
742                        return false;
743                    }
744                } catch (error) {
745                    console.error("Error en la solicitud:", error.message);
746                }
747            }
748            return await fetchData();
749        };
750
751        // En lugar de llamar a get_config_widget() directamente, se llama a loadWidgetConfig()
752        loadWidgetConfig();
753    }
754
755    //Dispatch: voz | whatsapp | chat de texto
756    if (mode === 'voice') {
757        load_voice_widget();
758    } else if (whatsapp_type && whatsapp_number && whatsapp_type == 'full' && whatsapp_number != '') {
759        load_whatsapp(whatsapp_number);
760    } else {
761        load_ia_widget();
762    }
763})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.