Registros interactivos en vivo: visualización de registros en el kit Voximplant



Nosotros continuamos actualizar el Kit Voximplant con JointJS . Y nos complace anunciar la aparición de registros de llamadas en vivo. Cuánto están vivos y si son peligrosos para los usuarios comunes, lea debajo del corte.



Anteriormente, solo los registros de llamadas estaban disponibles para que los usuarios analizaran las llamadas en el kit Voximplant. Además del audio, queríamos hacer no solo un registro de texto, sino una herramienta más conveniente para ver detalles de llamadas y analizar errores. Y dado que estamos tratando con un producto de código bajo / sin código, surgió la idea de visualizar registros.



¿Qué es la sal? / Nuevo concepto



Todos los resultados de las llamadas ahora están disponibles como una cadena de bloques pasados, animados de manera similar al modo de demostración. Solo la ruta se resalta aquí de antemano: puede rastrear visualmente cómo se movió el cliente a través del script.





Para hacer esto, vaya a la pestaña del historial de llamadas salientes o entrantes o en el informe de la campaña seleccionada y haga clic en "Ver registro". Y luego el editor demostrará lo que sucedió en la llamada paso a paso.





Controlar



Los controles detienen / inician (1) detienen / reanudan la reproducción, y atrás / siguiente (2) mueven al usuario de manera puntual al comienzo del bloque anterior / siguiente. También puede hacer clic en la línea de tiempo para comenzar la reproducción desde un punto específico en el tiempo, al igual que reproducir una canción.



Si el escenario incluye una grabación de una conversación, se reproducirá en paralelo al moverse a través de los bloques. La grabación de audio en la línea de tiempo se resalta en un color separado (3).





Para la comodidad del usuario, también está disponible una lista de bloques pasados ​​con marcas de tiempo ("Registro"):





Spoiler:

En la pestaña "Log", planeamos mostrar los detalles de los bloques. Nos ayudarán a comprender por qué cierto puerto salió del bloque y si hubo errores. Por ejemplo, para la unidad de reconocimiento, veremos los resultados y los errores de reconocimiento.

Los bloques complejos como DialogFlowConnector, IVR, ASR, etc. serán de gran interés aquí.




Variables



Las variables modificadas se muestran a la izquierda como notificaciones emergentes según la cronología. Es decir, si pasamos al bloque "Cambiar datos", aparecerán las variables que cambiaron allí. Nos alejaremos de él (más de 4 segundos en la línea de tiempo): las variables desaparecerán hasta que volvamos a encontrarnos en el intervalo donde ocurrió el cambio:







Truco de vida



Los registros de llamadas se mantienen en su forma original incluso después de cambios o eliminaciones de guiones. Y esto significa que no habrá problemas con la restauración de la lógica del script: si es necesario, siempre puede recurrir al registro.



Puede sentir los registros usted mismo en el kit Voximplant .



Entonces, ¿qué hay dentro?



Veamos cómo se implementan los registros dinámicos en el código. Digamos que desde Joint JS tomamos solo animación y selección de bloque, como en el modo de demostración. El resto (lo que se puede hacer sobre la base de esto) es nuestra imaginación.



Por cierto, para obtener más información sobre los aspectos internos del modo de demostración, lea nuestro artículo anterior .


Obtenemos puntos de tiempo



Cuando va a ver el registro, el servidor envía datos, que contienen una lista de todos los bloques pasados, la fecha y hora de entrada en ellos y una lista de variables que cambiaron durante la llamada. En otras palabras, en el frente, obtenemos dos matrices de objetos: log_path y log_variables.



También en la respuesta del servidor hay un enlace al audio y su duración, si se grabó la conversación.





Según el tiempo de entrada en los bloques, calculamos los puntos de tiempo en milisegundos y los escribimos para cada bloque y variables. Punto de referencia (0 ms): tiempo de entrada en el primer bloque. Entonces, si ingresamos al segundo bloque 5 segundos después del inicio de la llamada, entonces el punto de tiempo del segundo bloque = 5000 ms. Usando estos puntos de tiempo, calculamos la duración total del registro.



Actualizando la línea de tiempo



Después de presionar el botón de reproducción, la línea de tiempo comienza a actualizarse cada 10 ms. Durante cada actualización, verificamos si la hora actual coincide con uno de los puntos de tiempo:



const found = this.timePoints.find((item) => item === this.playTime);


Si hay una coincidencia, buscaremos todos los bloques cuyo punto de tiempo = tiempo actual + 600 ms (el tiempo durante el cual se produce la animación del movimiento entre bloques).



El código para el método updatePlayTime ():



updatePlayTime(): void {
    const interval = 10;
    let expected = Date.now() + interval;

    const tick = () => {
        const drift = Date.now() - expected;
        const found = this.timePoints.find((item) => item === this.playTime);
        this.$emit('update', {
            time: this.playTime,
            found: found !== undefined
        });

        if (this.playTime >= this.duration) {
            this.isPlay = false;
            this.playTime = this.duration;
            clearTimeout(this.playInterval);
            this.$emit('end', this.playTime);
            return;
        }

        expected += interval;

        this.playTime += 0.01;
        this.playTime = +this.playTime.toFixed(2);

        this.updateProgress();

        this.playInterval = window.setTimeout(tick, Math.max(0, interval - drift));
    };

    this.playInterval = window.setTimeout(tick, 10);
}


Además, cada 90 ms verificamos las coincidencias para la hora actual y los puntos de tiempo para las variables cambiadas + 4000 ms (el tiempo durante el cual se cuelga la notificación sobre el cambio de la variable).



Seleccionar bloques



Después de encontrar todas las coincidencias, agregue bloques a la cola para la selección e inicie la animación de los enlaces.



Si hay varios bloques con punto de tiempo = tiempo actual + 600 ms, entonces la transición se anima solo al último:



if (i === blocks.length - 1) {
    await this.selectBlock(blocks[i], 600, true, true);
}


Esto es necesario porque hay bloques que se procesan muy rápidamente. Por ejemplo, "Verificar datos", "Cambiar datos", etc. - en 1 segundo se pueden pasar varios bloques a la vez. Si los anima secuencialmente, habrá un retraso desde el tiempo de la línea de tiempo.



Código de método OnUpdateTimeline:



async onUpdateTimeline({
    time,
    found
}) {
    this.logTimer = time * 1000; //   
    this.checkHistoryNotify();

    if (!found) return;

    //        + 600
    const blocks = this.callHistory.log_path.filter((item) => {
        return item.timepoint >= this.logTimer && item.timepoint < this.logTimer + 600;
    });

    if (blocks.length) {
        this.editor.unselectAll();

        for (let i = 0; i < blocks.length; i++) {

            if (i === blocks.length - 1) {
                await this.selectBlock(blocks[i], 600, true, true);

                const cell = this.editor.getCellById(blocks[i].idTarget);
                this.editor.select(cell);
            } else {
                await this.selectBlock(blocks[i], 0, false, true);
            }
        }
    }
}


Entonces, en un círculo, hay una coincidencia: seleccionamos bloques, si el bloque ya está en la cola, no hacemos nada.



El método selectBlock () nos ayuda con esto:



async selectBlock(voxHistory, timeout = 700, animate = true, animateLink = true) {
    const inQueue = this.selectQueue.find((item) => item[0].targetId === voxHistory.idTarget);

    if (!inQueue) this.selectQueue.push(arguments);

    return this.exeQueue();
}


Rebobinar



Al rebobinar, el mismo principio: cuando se mueve la línea de tiempo, obtenemos el tiempo para el que necesitamos rebobinar y eliminar de los bloques seleccionados con puntos de tiempo más largos que el tiempo actual:



const forSelect = this.callHistory.log_path.filter((item) => {
        const time = accurate ? item.accurateTime : item.timepoint;
        return time <= this.logTimer;
    });


Hacemos una transición animada a la última.



Código del método OnRewind ():



async onRewind({
    time,
    accurate
}, animation = true) {
    this.editor.unselectAll();
    this.stopLinksAnimation();
    this.checkHistoryNotify(true);

    const forSelect = this.callHistory.log_path.filter((item) => {
        const time = accurate ? item.accurateTime : item.timepoint;
        return time <= this.logTimer;
    });

    for (let i = 0; i < forSelect.length; i++) {
        if (i === forSelect.length - 1) {
            await this.selectBlock(forSelect[i], 600, animation, false);
            const cell = this.editor.getCellById(forSelect[i].idTarget);
            this.editor.select(cell);
        } else {
            await this.selectBlock(forSelect[i], 0, false, false);
        }
    }

    if (this.isPlay) this.restartAnimateLink();

    this.onEndTimeline();
}


Reproducir audio



Encender / apagar la grabación de audio es aún más fácil. Si la línea de tiempo coincide con el inicio de la grabación, comienza a reproducirse y luego se sincroniza la hora. El método updatePlayer () es responsable de esto:



updatePlayer() {
    if (this.playTime >= this.recordStart && this.player.paused && !this.isEndAudio) {
        this.player.play();
        this.player.currentTime = this.playTime - this.recordStart;
    } else if (this.playTime < this.recordStart && !this.player.paused) {
        this.player.pause();
    }
}


¡Eso es todo! Así es como aparecieron los registros en vivo basados ​​en los métodos de Joint JS y la creatividad de nuestros desarrolladores. Asegúrese de probarlos usted mismo si aún no lo ha hecho :)



Genial si le gusta nuestra serie de artículos sobre actualizaciones de Whale. ¡Seguiremos compartiendo contigo lo más fresco e interesante!



All Articles