Desenvolvimento de um Assistente Virtual de Respostas Automáticas com jQuery

Em diversos cenários de atendimento web, é comum a necessidade de integrar um componente visual que simule um atendente virtual, capaz de interagir com vsiitantes através de mensagens pré-definidas ou sorteio aleatório. A implementação a seguir demonstra como construir esse módulo utilizando JavaScript moderno aliado à biblioteca jQuery, garantindo baixo acoplamento e alta capacidade de personalização.

Implementação da Classe Principal

O código foi reescrito utilizando o padrão ES6 para melhorar a organização e a reutilização. As configurações são centralizadas em um objeto de parâmetros, e a lógica de sorteio foi substituída pelo algoritmo Fisher-Yates para garantir maior imprevisibilidade na ordem das mensagens.


class AssistenteVirtual {
    constructor(config) {
        if (typeof jQuery === 'undefined') {
            throw new Error('jQuery deve ser carregado antes da inicialização.');
        }
        this.$ = jQuery;
        this.cfg = {
            painel: config.painel || 'bot-panel',
            historico: config.historico || 'chat-log',
            btnAtivar: config.btnAtivar || 'toggle-bot',
            btnDesativar: config.btnDesativar || 'close-bot',
            campoTexto: config.campoTexto || 'msg-input',
            btnEnviar: config.btnEnviar || 'send-msg',
            repliesBase: config.repliesBase || ['Olá! Como posso ajudar?', 'Bem-vindo ao nosso suporte.', 'Estamos online.', 'Até logo!'],
            estiloBot: config.estiloBot || 'reply-bubble',
            estiloUser: config.estiloUser || 'user-bubble',
            autoRespostaInicial: config.autoRespostaInicial !== false,
            alternarBotoes: config.alternarBotoes !== false,
            respostaFixa: config.respostaFixa || null
        };
        this.$painel = this.$('.' + this.cfg.painel);
        this.$log = this.$('.' + this.cfg.historico);
        this.$campo = this.$('.' + this.cfg.campoTexto);
        this.validarDOM();
        this.ancorarEventos();
        this.gerarPermutacao();
        this.posicao = 0;
        this.tempoEspera = null;
    }

    validarDOM() {
        const chaves = [this.cfg.painel, this.cfg.historico, this.cfg.campoTexto, this.cfg.btnAtivar];
        chaves.forEach(classe => {
            if (this.$('.' + classe).length === 0) {
                throw new Error(`Seletor .${classe} não encontrado no DOM.`);
            }
        });
    }

    ancorarEventos() {
        this.$('.' + this.cfg.btnAtivar).on('click', () => this.exibir());
        this.$('.' + this.cfg.btnDesativar).on('click', () => this.ocultar());
        this.$('.' + this.cfg.btnEnviar).on('click', () => this.processarEnvio());
        this.$campo.on('keydown', (e) => {
            if (e.key === 'Enter') this.processarEnvio();
        });
    }

    exibir() {
        this.$painel.fadeIn(300);
        if (this.cfg.alternarBotoes) {
            this.$('.' + this.cfg.btnAtivar).hide();
            this.$('.' + this.cfg.btnDesativar).show();
        }
        if (this.cfg.autoRespostaInicial) {
            this.dispararResposta();
        }
    }

    ocultar() {
        clearTimeout(this.tempoEspera);
        this.$painel.fadeOut(200);
        if (this.cfg.alternarBotoes) {
            this.$('.' + this.cfg.btnDesativar).hide();
            this.$('.' + this.cfg.btnAtivar).show();
        }
    }

    processarEnvio() {
        const valor = this.$campo.val().trim();
        if (!valor) return;
        this.adicionarAoLog(valor, this.cfg.estiloUser);
        this.$campo.val('');
        this.interpretarComando(valor);
    }

    interpretarComando(texto) {
        // Ponto de extensão para lógica de negócio ou chamadas HTTP
        if (texto.toLowerCase() === 'preço') {
            this.dispararResposta('Nossos planos começam a partir de R$ 49,90/mês.');
            return;
        }
        this.dispararResposta();
    }

    adicionarAoLog(texto, classe) {
        this.$log.append(`<div class="${classe}"><p>${texto}</p></div>`);
        this.atualizarScroll();
    }

    atualizarScroll() {
        this.$log.stop().animate({ scrollTop: this.$log.prop('scrollHeight') }, 300);
    }

    dispararResposta(textoPersonalizado = null) {
        clearTimeout(this.tempoEspera);
        this.tempoEspera = setTimeout(() => {
            if (textoPersonalizado) {
                this.adicionarAoLog(textoPersonalizado, this.cfg.estiloBot);
                return;
            }
            const conteudo = this.cfg.repliesBase[this.permutacao[this.posicao]];
            this.adicionarAoLog(conteudo, this.cfg.estiloBot);
            this.posicao = (this.posicao + 1) % this.cfg.repliesBase.length;
            if (this.posicao === 0) this.gerarPermutacao();
        }, 1400);
    }

    gerarPermutacao() {
        this.permutacao = [...Array(this.cfg.repliesBase.length).keys()];
        for (let i = this.permutacao.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [this.permutacao[i], this.permutacao[j]] = [this.permutacao[j], this.permutacao[i]];
        }
    }
}

Configuração e Instanciação

A personalização do comportamento ocorre principalmente através do objeto de configuração ou sobrescrevendo o método interpretarComando após a criação da instância. Isso permite adaptar o fluxo de conversa sem modificar o núcleo da classe.


const bot = new AssistenteVirtual({
    painel: 'chat-container',
    historico: 'message-stream',
    btnAtivar: 'ico-support',
    btnDesativar: 'x-close',
    campoTexto: 'user-typing',
    btnEnviar: 'submit-query',
    repliesBase: [
        'Olá! Estou aqui para auxiliar.',
        'Descreva sua necessidade e retornarei em instantes.',
        'Nossa equipe técnica está disponível.',
        'Foi um prazer ajudar!'
    ],
    estiloBot: 'atendente-msg',
    estiloUser: 'visitante-msg',
    autoRespostaInicial: true
});

Estrutura HTML e Estilização

O exemplo abaixo ilustra a marcação semântica e o CSS necessário para renderizar a interface. As classes utilizadas correspondem aos valores padrão definidos na configuração.



<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bot de Atendimento</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <style>
        body { font-family: system-ui, sans-serif; margin: 0; background: #eef2f5; }
        .launcher { position: fixed; right: 24px; bottom: 24px; width: 56px; height: 56px; background: #2563eb; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); font-size: 24px; }
        .panel { position: fixed; right: 24px; bottom: 90px; width: 340px; height: 420px; background: #fff; border-radius: 16px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); display: none; flex-direction: column; overflow: hidden; }
        .top-bar { padding: 14px; background: #2563eb; color: #fff; display: flex; justify-content: space-between; align-items: center; font-weight: 600; }
        .top-bar button { background: transparent; border: none; color: #fff; cursor: pointer; font-size: 18px; }
        .stream { flex: 1; padding: 12px; overflow-y: auto; background: #f8fafc; }
        .stream::after { content: ""; display: table; clear: both; }
        .bottom-bar { display: flex; border-top: 1px solid #e2e8f0; }
        .bottom-bar input { flex: 1; padding: 12px; border: none; outline: none; font-size: 14px; }
        .bottom-bar button { padding: 0 16px; background: #2563eb; color: #fff; border: none; cursor: pointer; font-weight: 500; }
        .atendente-msg, .visitante-msg { margin: 10px 0; }
        .atendente-msg p { background: #e2e8f0; padding: 10px 14px; border-radius: 14px 14px 14px 2px; max-width: 85%; float: left; font-size: 14px; }
        .visitante-msg p { background: #2563eb; color: #fff; padding: 10px 14px; border-radius: 14px 14px 2px 14px; max-width: 85%; float: right; font-size: 14px; }
    </style>
</head>
<body>
    <div class="launcher ico-support">💬</div>
    <div class="panel chat-container">
        <div class="top-bar">
            <span>Suporte Online</span>
            <button class="x-close">✕</button>
        </div>
        <div class="stream message-stream"></div>
        <div class="bottom-bar">
            <input type="text" class="user-typing" placeholder="Escreva aqui...">
            <button class="submit-query">Enviar</button>
        </div>
    </div>

    <script>
        // Inserir classe AssistenteVirtual aqui
        const bot = new AssistenteVirtual({ responderAoInicial: true });
    </script>
</body>
</html>

Mecanismo de Sorteio e Extensibilidade

A geração de índices aleatórios utiliza troca de posições em vetor para evitar padrões repetitivos. Quando o ciclo de mensagens é concluído, um novo embaralhamento é executado automaticamente. Para integrações com back-end, o método interpretarComando pode ser estendido com chamadas fetch ou AJAX, garantindo que a interface permaneça responsiva enquanto aguarda respostas assíncronas.

Tags: jQuery javascript Chatbot Automação Interface Web

Publicado em 10-6 16:38