Reconocimiento de voz para consultar una base de datos - página principal (index.html)

La página principal de nuestra aplicación está contenida en el archivo index.html y contiene todo el código HTML y JavaScript para interacturar con la interfaz de consultas por voz.

Veamos primero las partes fundamentales del archivo index.html.

index.html
<!DOCTYPE html>
<html lang="es">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Consulta por Voz</title>
  <link href="datatables/datatables.min.css" rel="stylesheet">
  <link href="bootstrap-4.3.1/css/bootstrap.min.css" rel="stylesheet">

  <script src="js/jquery-3.4.1.js"></script>
  <script src="js/popper.min.js"></script>
  <script src="bootstrap-4.3.1/js/bootstrap.min.js"></script>
  <script src="datatables/datatables.min.js"></script>
</head>

<body>
  <div class="container-fluid">

    <div class="row" style="margin:1em">
      <div class="col-12 text-center">
        <div class="form-group">
          <button type="button" id="Dicta" class="btn btn-success">Iniciar dictado de consulta</button>
        </div>
        <div class="form-check">
          <input type="checkbox" class="form-check-input" id="SintesisDeVoz" checked>
          <label class="form-check-label" for="SitesisDeVoz">Activar síntesis de voz.</label>
        </div>
        <div>
          <textarea class="form-control rounded-0" id="TextArea1" rows="3"></textarea>
          <button type="button" id="Consulta" class="btn btn-success" style="margin:1em">Consultar</button>
          <table id="tabla1" class="table  table-dark responsive" width="100%">
          </table>
          <div class="form-group">
            <button type="button" class="btn btn-primary" id="BotonDetenerSintesis">Detener locución</button>
          </div>
        </div>
      </div>
    </div>

  </div>
  <script>

    document.addEventListener("DOMContentLoaded", function () {
      $("#Consulta").hide();
      $("#BotonDetenerSintesis").hide();

      const reconocimiento = new webkitSpeechRecognition();
      reconocimiento.lang = "es-ES";
      reconocimiento.continuous = false;
      reconocimiento.interimResults = true;
      reconocimiento.onresult = function (event) {
        document.getElementById("TextArea1").value = "";
        for (var i = event.resultIndex; i < event.results.length; i++) {
          if (event.results[i].isFinal) {
            document.getElementById("TextArea1").value += event.results[i][0].transcript;
          }
        }
      }

      $('#Dicta').click(function () {
        reconocimiento.start();
      });

      $('#Consulta').click(function () {
        let oracion = document.getElementById("TextArea1").value;
        consultar(oracion);
        if (tablacreada) {
          $('#tabla1').DataTable().destroy();
          $('#tabla1').empty();
          tablacreada = false;
        }
        $("#Consulta").hide();
      });

      reconocimiento.onend = function (event) {
        let oracion = document.getElementById("TextArea1").value;
        if (tablacreada) {
          $('#tabla1').DataTable().destroy();
          $('#tabla1').empty();
          tablacreada = false;
        }
        consultar(oracion);
      }

      var tablacreada = false;
      var tabla1;
      function consultar(ora) {
        $.ajax({
          type: 'GET',
          url: 'consulta.php?oracion=' + ora,
          data: '',
          success: function (datos) {
            if (datos.length > 0) {
              if ($('#SintesisDeVoz').is(':checked'))
                activaSintesisDeVoz(datos);
              if (tablacreada) {
                $('#tabla1').DataTable().destroy();
                $('#tabla1').empty();
              }
              var columnas = [];
              NombreColumna = Object.keys(datos[0]);
              for (var i in NombreColumna) {
                columnas.push({
                  data: NombreColumna[i],
                  title: primerCaracterMayuscula(NombreColumna[i])
                });
              }

              tabla1 = $('#tabla1').DataTable({
                data: datos,
                columns: columnas,
                lengthChange: false,
                searching: false,
                info: false,
                paging: false,
                ordering: false,
                "language": {
                  "url": "DataTables/spanish.json",
                },
              });
              tablacreada = true;
            }
            else
              alert("No hay datos para esa pregunta");
          },
          error: function () {
            if (tablacreada) {
              tablacreada = false;
            }
            alert("No hay respuesta para esa pregunta");
          }
        });
      }

      $("#TextArea1").focus(function () {
        $("#Consulta").show();
      });

      function activaSintesisDeVoz(data) {
        $("#BotonDetenerSintesis").show();
        let speech;
        for (let indice = 0; indice < data.length; indice++)
          for (let [key, value] of Object.entries(data[indice])) {
            speech = new SpeechSynthesisUtterance(key);
            speech.lang = 'es-ES';
            window.speechSynthesis.speak(speech);
            speech = new SpeechSynthesisUtterance(value);
            speech.lang = 'es-ES';
            window.speechSynthesis.speak(speech);
          }
        speech.onend = function (evento) {
          $("#BotonDetenerSintesis").hide();
        };

      }

      $('#BotonDetenerSintesis').click(function () {
        window.speechSynthesis.cancel();
        $("#BotonDetenerSintesis").hide();
      });

      function primerCaracterMayuscula(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
      }

    });
  </script>
</body>

</html>

Disponemos de un formulario HTML que inicialmente muestra el botón de "Iniciar dictado de consulta", el CheckBox de activar la síntesis de voz y finalmente un control de tipo TextArea donde se carga ya sea por teclado o mediante el dictado la consulta a la base de datos de facturación.

Hay otros dos botones que inicialmente se encuentran ocultos que son el de "Detener locución" y el de "Consultar":

  <div class="container-fluid">

    <div class="row" style="margin:1em">
      <div class="col-12 text-center">
        <div class="form-group">
          <button type="button" id="Dicta" class="btn btn-success">Iniciar dictado de consulta</button>
        </div>
        <div class="form-check">
          <input type="checkbox" class="form-check-input" id="SintesisDeVoz" checked>
          <label class="form-check-label" for="SitesisDeVoz">Activar síntesis de voz.</label>
        </div>
        <div>
          <textarea class="form-control rounded-0" id="TextArea1" rows="3"></textarea>
          <button type="button" id="Consulta" class="btn btn-success" style="margin:1em">Consultar</button>
          <table id="tabla1" class="table  table-dark responsive" width="100%">
          </table>
          <div class="form-group">
            <button type="button" class="btn btn-primary" id="BotonDetenerSintesis">Detener locución</button>
          </div>
        </div>
      </div>
    </div>

  </div>

El bloque de JavaScript que se ejecuta una vez cargado el DOM de la página, lo primero que hacemos es ocultar los dos botones:

    document.addEventListener("DOMContentLoaded", function () {
      $("#Consulta").hide();
      $("#BotonDetenerSintesis").hide();

Seguidamente procedemos a crear un objeto de la clase 'webkitSpeechRecognition' y configurar una serie de propiedades del mismo:

      const reconocimiento = new webkitSpeechRecognition();

Mediante la propiedad 'lang' especificamos el lenguaje que debe reconocer:

      reconocimiento.lang = "es-ES";

La propiedad 'continuous' especifica con el valor 'false' que cuando hagamos una pausa se detenga el reconocimento de la voz y se de por finalizada:

      reconocimiento.continuous = false;

Luego fijamos la propiedad 'interimResults' con el valor 'true', con esto el reconocimiento de voz nos entrega resultados parciales previos a la finalización:

      reconocimiento.interimResults = true;

A la propiedad 'onresult' debemos asignarle una función que se llamará cada vez que se tengan resultados parciales del reconocimento de voz, en la misma iremos mostrando todos los resultados parciales hasta que el usuario realice una pausa y se de por finalizado el ingreso de la frase (dentro de la función cargamos la frase ingresada en el control de formulario 'TextArea'):

      reconocimiento.onresult = function (event) {
        document.getElementById("TextArea1").value = "";
        for (var i = event.resultIndex; i < event.results.length; i++) {
          if (event.results[i].isFinal) {
            document.getElementById("TextArea1").value += event.results[i][0].transcript;
          }
        }
      }

La propiedad 'onend' almacena la función que se llamará cuando el usuario termine de dictar la frase. En la misma procedemos a recuperar del control 'TextArea' la oración dictada, verificamos la bandera 'tablacreada' almacena un true para eliminar la tabla creada en una consulta previa. Finalmente llamamos a la función 'consultar':

      reconocimiento.onend = function (event) {
        let oracion = document.getElementById("TextArea1").value;
        if (tablacreada) {
          $('#tabla1').DataTable().destroy();
          $('#tabla1').empty();
          tablacreada = false;
        }
        consultar(oracion);
      }

La función 'consultar' procede a efectuar una llamada AJAX pasando por parámetro GET la oración que ha dictado el usuario:

      function consultar(ora) {
        $.ajax({
          type: 'GET',
          url: 'consulta.php?oracion=' + ora,
          data: '',

Cuando el servidor responde procedemos a crear la tabla en forma dinámica ya que no sabemos cuantas columnas tendrá de antemano:

          success: function (datos) {
            if (datos.length > 0) {
              if ($('#SintesisDeVoz').is(':checked'))
                activaSintesisDeVoz(datos);
              if (tablacreada) {
                $('#tabla1').DataTable().destroy();
                $('#tabla1').empty();
              }
              var columnas = [];
              NombreColumna = Object.keys(datos[0]);
              for (var i in NombreColumna) {
                columnas.push({
                  data: NombreColumna[i],
                  title: primerCaracterMayuscula(NombreColumna[i])
                });
              }

              tabla1 = $('#tabla1').DataTable({
                data: datos,
                columns: columnas,
                lengthChange: false,
                searching: false,
                info: false,
                paging: false,
                ordering: false,
                "language": {
                  "url": "DataTables/spanish.json",
                },
              });
              tablacreada = true;
            }
            else
              alert("No hay datos para esa pregunta");
          },
          error: function () {
            if (tablacreada) {
              tablacreada = false;
            }
            alert("No hay respuesta para esa pregunta");
          }
        });
      }

Un método importante de la clase 'webkitSpeechRecognition' es el 'start' que tiene por objetivo iniciar el proceso de escuchar la frase del operador:

      $('#Dicta').click(function () {
        reconocimiento.start();
      });

El algoritmo del botón de 'Consulta' se dispara si el usuario desea ingresar una oración por teclado y no por comando de voz:

      $('#Consulta').click(function () {
        let oracion = document.getElementById("TextArea1").value;
        consultar(oracion);
        if (tablacreada) {
          $('#tabla1').DataTable().destroy();
          $('#tabla1').empty();
          tablacreada = false;
        }
        $("#Consulta").hide();
      });

El botón de "Consulta" se hace visible si el usuario hace foco en el mismo:

      $("#TextArea1").focus(function () {
        $("#Consulta").show();
      });

La función de síntesis de voz tiene por objetivo de leer en voz alta el resultado de la consulta haciendo uso de la clase 'SpeechSynthesisUtterance':

      function activaSintesisDeVoz(data) {
        $("#BotonDetenerSintesis").show();
        let speech;
        for (let indice = 0; indice < data.length; indice++)
          for (let [key, value] of Object.entries(data[indice])) {
            speech = new SpeechSynthesisUtterance(key);
            speech.lang = 'es-ES';
            window.speechSynthesis.speak(speech);
            speech = new SpeechSynthesisUtterance(value);
            speech.lang = 'es-ES';
            window.speechSynthesis.speak(speech);
          }
        speech.onend = function (evento) {
          $("#BotonDetenerSintesis").hide();
        };

      }

En el próximo concepto veremos como desde PHP recibimos la oración y procedemos a retornar un resultado que vimos que se muestra en el DataTable.