Logs interativos "ao vivo": visualização de logs no Voximplant Kit



Nós continuar a atualizar o Kit Voximplant com JointJS . E temos o prazer de anunciar a aparência de registros de chamadas "ao vivo". Quão animado e perigoso para usuários comuns, leia abaixo.



Anteriormente, apenas as gravações de chamadas estavam disponíveis para os usuários analisarem as chamadas no kit Voximplant. Além do áudio, queríamos criar não apenas um registro de texto, mas uma ferramenta mais conveniente para exibir detalhes da chamada e analisar erros. E como estamos lidando com um produto de código baixo / sem código, surgiu a idéia de visualizar logs.



Qual é o sal? / Novo conceito



Todos os resultados das chamadas estão agora disponíveis como uma cadeia de blocos concluídos, animados por analogia com o modo de demonstração. Somente o caminho é destacado aqui com antecedência: é possível rastrear visualmente como o cliente se moveu pelo script.





Para fazer isso, vá para a guia do histórico de chamadas efetuadas ou recebidas ou no relatório da campanha selecionada e clique em "Visualizar log". E então o editor demonstrará o que aconteceu na chamada passo a passo.





Ao controle



Os controles de parada / partida (1) param / retomam a reprodução e voltam \ mais (2) movem o usuário no sentido horário para o início do bloco anterior / seguinte. Você também pode simplesmente clicar na linha do tempo para iniciar a reprodução a partir de um momento específico, como na reprodução de uma música.



Se o cenário incluir a gravação de uma conversa, ela será reproduzida em paralelo com a movimentação pelos blocos. A gravação de áudio na linha do tempo é destacada em uma cor separada (3).





Para conveniência do usuário, também está disponível uma lista de blocos passados ​​com registro de data e hora ("Log"):





Spoiler:

Na aba “Log”, planejamos mostrar os detalhes dos blocos. Eles nos ajudarão a entender por que o bloco saiu em uma porta específica e se houve algum erro. Por exemplo, para um bloco de reconhecimento, veremos resultados e erros de reconhecimento.

Blocos complexos, como DialogFlowConnector, IVR, ASR, etc., serão de grande interesse aqui.




Variáveis



As variáveis ​​alteradas são exibidas à esquerda na forma de pop-up de notificações, de acordo com a cronologia. Ou seja, se passarmos para o bloco "Alterar dados", as variáveis ​​que foram alteradas serão exibidas. Vamos longe disso (mais de 4s na linha do tempo) - as variáveis ​​desaparecerão até que nos encontremos novamente no intervalo em que a alteração ocorreu:







Life hack



Os registros de chamadas são mantidos em sua forma original, mesmo após alterar ou excluir um script. Isso significa que não haverá problemas com a restauração da lógica do script: se necessário, você sempre pode consultar o log.



Você pode sentir os registros no kit Voximplant .



Então, o que tem dentro?



Vamos ver como os logs dinâmicos são implementados no código. Digamos imediatamente que, no Joint JS, pegamos apenas animação e alocação de blocos, como no modo de demonstração. O resto (o que pode ser feito com base nisso) é nossa imaginação.



A propósito, para saber mais sobre as partes internas do modo de demonstração, leia nosso artigo anterior .


Temos pontos no tempo



Quando você visualiza o log, o servidor envia dados, que contêm uma lista de todos os blocos passados, a data e a hora da entrada neles e uma lista de variáveis ​​que foram alteradas durante a chamada. Em outras palavras, na frente, temos duas matrizes de objetos: log_path e log_variables.



Além disso, a resposta do servidor contém um link para o áudio e sua duração, se a conversa foi gravada.





Com base no tempo de entrada nos blocos, calculamos pontos de tempo em milissegundos e os escrevemos para cada bloco e variáveis. O ponto de referência (0 ms) é a hora de inserir o primeiro bloco. Portanto, se inserimos o segundo bloco 5 segundos após o início da chamada, o ponto no tempo do segundo bloco = 5000 ms. Usando esses pontos no tempo, calculamos a duração total do log.



Atualizando a linha do tempo



Depois de pressionar o botão play, a linha do tempo começa a ser atualizada a cada 10 ms. Durante cada atualização, verificamos se o horário atual corresponde a um dos pontos no tempo:



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


Se houver uma correspondência, procuraremos todos os blocos para os quais o ponto do tempo = hora atual + 600 ms (o tempo durante o qual a animação se move entre os blocos).



O código para o 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);
}


Além disso, a cada 90 ms, verificamos a coincidência da hora e dos pontos de tempo atuais das variáveis ​​alteradas + 4000 ms (o tempo durante o qual a notificação sobre a alteração da variável é interrompida).



Selecione os blocos



Depois que todas as correspondências forem encontradas, adicione os blocos à fila para selecionar e iniciar a animação do link.



Se houver vários blocos com ponto no tempo = tempo atual + 600 ms, a transição será animada apenas para o último:



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


Isso é necessário porque existem blocos que são processados ​​muito rapidamente. Por exemplo, "Verificação de dados", "Alterar dados" etc. - vários blocos podem ser percorridos em 1 segundo. Se você os animar sequencialmente, haverá um atraso em relação ao tempo da linha do tempo.



Código do 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);
            }
        }
    }
}


E assim, em um círculo, há uma coincidência - selecionamos blocos, se um bloco já estiver na fila - não fazemos nada.



O método selectBlock () nos ajuda com isso:



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



Ao rebobinar, o princípio é o mesmo: quando a linha do tempo é movida, temos tempo para rebobinar e remover dos blocos selecionados com pontos de tempo maiores que o tempo atual:



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


Fazemos uma transição animada para a última.



Código do 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();
}


Reproduzir áudio



Ligar / desligar a gravação de áudio é ainda mais fácil. Se a linha do tempo coincidir com o início da gravação, ela começará a ser reproduzida e a hora será sincronizada. O método updatePlayer () é responsável por isso:



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


Isso é tudo! Foi assim que os logs ativos apareceram com base nos métodos Joint JS e na criatividade de nossos desenvolvedores. Teste você mesmo se ainda não o fez :)



Ótimo se você curte nossa série de artigos sobre as atualizações do Keith. Continuaremos compartilhando com você o mais recente e o mais interessante!



All Articles