Journaux interactifs "live": visualisation des logs dans Voximplant Kit



Nous continuons à mettre à jour Voximplant Kit avec JointJS . Et nous sommes heureux d'annoncer l'apparition de journaux d'appels "en direct". Combien ils sont vivants et s'ils sont dangereux pour les utilisateurs ordinaires, lisez sous la coupe.



Auparavant, seuls les enregistrements d'appels étaient à la disposition des utilisateurs pour analyser les appels dans Voximplant Kit. En plus de l'audio, nous voulions créer non seulement un journal de texte, mais un outil plus pratique pour afficher les détails des appels et analyser les erreurs. Et comme il s'agit d'un produit low-code / no-code, l'idée de visualiser les logs est apparue.



Quel est le sel? / Nouveau concept



Tous les résultats des appels sont désormais disponibles sous la forme d'une chaîne de blocs passés, animés de la même manière que le mode démo. Seul le chemin est mis en évidence ici à l'avance: vous pouvez suivre visuellement comment le client s'est déplacé dans le script.





Pour ce faire, accédez à l'onglet de l'historique des appels sortants ou entrants ou dans le rapport de la campagne sélectionnée et cliquez sur "Afficher le journal". Et puis l'éditeur montrera étape par étape ce qui s'est passé lors de l'appel.





Contrôle



Les commandes d'arrêt / démarrage (1) arrêtent / reprennent la lecture, et retour \ plus loin (2) par point déplacent l'utilisateur au début du bloc précédent / suivant. Vous pouvez également simplement cliquer sur la chronologie pour démarrer la lecture à partir d'un moment précis, tout comme la lecture d'une chanson.



Si le scénario comprend un enregistrement d'une conversation, il sera joué en parallèle avec le déplacement à travers les blocs. L'enregistrement audio sur la timeline est mis en surbrillance dans une couleur distincte (3).





Pour la commodité de l'utilisateur, une liste des blocs passés avec des horodatages est également disponible ("Journal"):





Spoiler:

Dans l'onglet «Log», nous prévoyons d'afficher les détails des blocs. Ils nous aideront à comprendre pourquoi le bloc s'est arrêté sur un port spécifique et s'il y a eu des erreurs. Par exemple, pour l'unité de reconnaissance, nous verrons les résultats et les erreurs de reconnaissance.

Les blocs complexes tels que DialogFlowConnector, IVR, ASR, etc. seront du plus grand intérêt ici.




Variables



Les variables modifiées sont affichées à gauche sous forme de pop-up de notifications selon la chronologie. Autrement dit, si nous passons au bloc «Modifier les données», les variables qui y ont changé apparaîtront. Allons loin de là (plus de 4s sur la timeline) - les variables disparaîtront jusqu'à ce que nous nous retrouvions dans l'intervalle où le changement s'est produit:







Piratage de la vie



Les journaux d'appels sont conservés dans leur forme d'origine même après des modifications ou des suppressions de script. Cela signifie qu'il n'y aura aucun problème avec la restauration de la logique du script: si nécessaire, vous pouvez toujours vous référer au journal.



Vous pouvez sentir les journaux vous-même sur le kit Voximplant .



Alors, qu'y a-t-il à l'intérieur?



Voyons comment les journaux dynamiques sont implémentés dans le code. Disons simplement que dans Joint JS, nous n'avons pris que l'animation et la sélection de blocs, comme dans le mode démo. Le reste (ce qui peut être fait sur cette base) est notre fantaisie.



Au fait, pour en savoir plus sur les fonctions internes du mode démo, lisez notre article précédent .


Nous obtenons des points temporels



Lorsque vous allez voir le journal, le serveur envoie des données, qui contiennent une liste de tous les blocs passés, la date et l'heure de leur entrée et une liste de variables qui ont changé pendant l'appel. En d'autres termes, à l'avant, nous obtenons deux tableaux d'objets: log_path et log_variables.



La réponse du serveur contient également un lien vers l’audio et sa durée, si la conversation a été enregistrée.





En fonction de l'heure d'entrée dans les blocs, nous calculons les points temporels en millisecondes et les écrivons pour chaque bloc et variables. Le point de référence (0 ms) est le temps pour entrer dans le premier bloc. Donc, si nous sommes entrés dans le deuxième bloc 5 secondes après le début de l'appel, alors le point de temps du deuxième bloc = 5000 ms. À l'aide de ces points temporels, nous calculons la durée totale du journal.



Mise à jour de la chronologie



Après avoir appuyé sur le bouton de lecture, la chronologie commence à se mettre à jour toutes les 10 ms. Lors de chaque mise à jour, nous vérifions si l'heure actuelle coïncide avec l'un des points temporels:



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


S'il y a correspondance, nous rechercherons tous les blocs dont le point de temps = temps courant + 600 ms (le temps pendant lequel l'animation de mouvement entre blocs se produit).



Le code de la méthode 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);
}


De plus, toutes les 90 ms, nous vérifions les coïncidences pour l'heure actuelle et les points de temps pour les variables modifiées + 4000 ms (le temps pendant lequel la notification du changement de variable se bloque).



Sélection de blocs



Une fois toutes les correspondances trouvées, ajoutez les blocs à la file d'attente pour sélectionner et démarrer l'animation du lien.



S'il y a plusieurs blocs avec point de temps = heure actuelle + 600 ms, la transition n'est animée que vers le dernier:



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


Ceci est nécessaire car il existe des blocs qui sont traités très rapidement. Par exemple, "Vérification des données", "Modification des données", etc. - en 1 seconde, plusieurs blocs peuvent être passés à la fois. Si vous les animez séquentiellement, il y aura un décalage par rapport à la chronologie.



Code de méthode 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);
            }
        }
    }
}


Et donc dans un cercle, il y a une coïncidence - nous sélectionnons des blocs, si un bloc est déjà dans la file d'attente - nous ne faisons rien.



La méthode selectBlock () nous aide avec ceci:



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();
}


Rembobiner



Lors du rembobinage, le principe est le même: lorsque la timeline est déplacée, on obtient le temps de rembobiner et de retirer des blocs sélectionnés avec des points temporels plus longs que l'heure actuelle:



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


Nous faisons une transition animée vers la dernière.



Code de la méthode 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();
}


Lire l'audio



Activer / désactiver l'enregistrement audio est encore plus facile. Si la chronologie coïncide avec le début de l'enregistrement, la lecture commence, puis l'heure est synchronisée. La méthode updatePlayer () est responsable de ceci:



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();
    }
}


C'est tout! Ainsi, basés sur les méthodes Joint JS et la créativité de nos développeurs, des journaux en direct sont apparus. Assurez-vous de les tester vous-même si vous ne l'avez pas déjà fait :)



Génial si vous aimez notre série d'articles sur les mises à jour de Keith. Nous continuerons à partager avec vous le plus frais et le plus intéressant!



All Articles