Documentación

El plugin funciona nada más ponerlo. Una línea de HTML pone un micrófono al lado de un campo, y todo lo demás de esta página es opcional.

Instalación rápida

¿Trabaja con Claude, Codex o cualquier otro agente? Copie y pegue sin más el siguiente prompt y delegue la instalación por completo.

Install AddSpeech on this site by following https://addspeech.com/skill.md

Esas instrucciones son una página corriente que puede abrir y leer antes de pegar nada. Averigua por su cuenta con qué está construido su proyecto, pregunta antes de escribir, y no se nos envía ninguna parte de su proyecto.

Si guarda esa misma página como skill, en ~/.claude/skills/addspeech/SKILL.md, podrá llamarla por su nombre en el siguiente sitio en vez de volver a pegar un enlace. En ambos casos es el mismo archivo.

Instalación

Todo lo que viene debajo cambia con esta elección: el paso de instalación, los ejemplos y cómo se escribe cada opción. Se recuerda en este navegador, así que la siguiente página ya se abrirá donde toca.

Sin paquete: solo la etiqueta añadida en mounted y retirada antes de unmount, para que un cambio de ruta no deje dos micrófonos detrás.

AddSpeech.vue

import { onBeforeUnmount, onMounted } from 'vue';

const { target = 'textarea' } = defineProps<{ target?: string }>();

let script: HTMLScriptElement | null = null;

onMounted(() => {
  script = document.createElement('script');

  script.src = 'https://cdn.addspeech.com/widget.js';
  script.dataset.key = 'as_pub_your_key_here';
  script.dataset.target = target;
  document.body.append(script);
});

onBeforeUnmount(() => {
  script?.remove();
  document.querySelector('addspeech-microphone')?.remove();
});

Está en el código fuente de la página, igual que una publishable key de Stripe o una key de Google Maps. Una key por dominio, y en esta instalación se puede leer ahí a propósito. Solo funciona en los dominios que figuran en su cuenta, así que quien la copie a su propio sitio no consigue nada.

Si prefiere que no llegue ahí nunca, su propio servidor puede guardar la key y entregar a cada página un token de corta duración por grabación. Es la opción session de más abajo, y es la instalación que recomendamos a cualquiera que tenga un servidor donde ponerla.

No hace falta nada más. Si no se le dice otra cosa, el micrófono rellena la textarea de la página - aquella en la que se estaba escribiendo por última vez, cuando hay varias - así que un formulario con una sola caja de mensaje no necesita ningún target. Añada data-target, un selector CSS como el que le pasaría a querySelector, cuando el campo sea un input en lugar de una textarea, o cuando solo una de varias textareas sea la suya. En una página donde no coincide nada, el botón no aparece en absoluto - así que la etiqueta script puede quedarse en la plantilla que usan todas las páginas, y el micrófono solo se muestra donde hay un formulario.

Requisitos

Una lista corta, que un sitio corriente ya cumple en su mayor parte.

La página tiene que servirse por HTTPS.

Los navegadores no le dan un micrófono a una página que no lo hace, y desde nuestro lado no hay nada que podamos hacer al respecto. Localhost queda exento, así que desarrollar en local funciona con normalidad.

El campo tiene que existir en la página cuando el script se ejecuta, y tiene que ser un input o un textarea de verdad.

Algo editable que no sea un control de formulario - un editor de texto enriquecido montado con divs - todavía no está soportado.

El campo necesita sitio para unos cuantos miles de caracteres.

La gente habla en frases mucho más largas de las que teclea, y un minuto de habla corriente ronda los mil - así que un maxlength de 255 o de 500 se come calladamente el final de cada respuesta. O quita el atributo o lo pone en unos 3000. El widget mira el campo al que se le ha apuntado y lo avisa en la consola del navegador, y en su página de cuenta, cuando encuentra uno demasiado pequeño.

La columna que hay detrás de ese campo necesita el mismo sitio.

Esta es la mitad que se suele pasar por alto: un formulario que en el navegador acepta novecientos caracteres, con un VARCHAR(255) detrás, descarta el final en el último paso, después de que la persona ya haya hablado. Use TEXT, o un VARCHAR lo bastante largo como para ir en serio, y revise cualquier regla de validación en el servidor que siga reflejando la longitud antigua.

Cualquier navegador de los últimos años.

Chrome, Edge, Firefox y Safari graban todos; en iOS funciona en Safari y en cualquier otro navegador del teléfono, porque por debajo todos son Safari.

Al visitante se le pide el micrófono una vez, y se lo pide su propio navegador; la respuesta se recuerda para su dominio.

Nosotros no vemos nunca ese diálogo y no podemos adelantarnos a él.

Cuando se agota el cupo del mes, el botón se retira de sus páginas hasta el día uno del mes siguiente.

Es intencionado y no un fallo. Sus formularios y sus áreas de texto siguen funcionando exactamente igual que antes de añadir el widget, así que un visitante ve que no hay micrófono en lugar de un micrófono que se niega. Su página de cuenta muestra lo consumido y cuándo se renueva el cupo.

React, Vue, Livewire, WordPress, una página HTML normal - da igual.

El widget escribe en el campo como lo haría una persona tecleando y después dispara los eventos input y change, así que un framework que vigile ese campo ve exactamente lo que espera ver.

Uso

Todo lo que se le puede decir al widget, dicho como atributos en la etiqueta script. Solo la key es obligatoria; el resto tiene valores por defecto elegidos para que omitir uno le dé algo razonable en lugar de algo roto.

Las filas de abajo nombran cada opción como la nombra una etiqueta script. En Vue la misma opción se escribe así:

script.dataset.theme = 'spotlight';
data-keyObligatorio

Su key, de su cuenta. Dice de qué sitio se trata y, por tanto, del cupo de quién sale la grabación.

data-targettextarea

Un selector CSS para el campo que hay que rellenar. Cualquier textarea de la página, salvo que diga otra cosa.

data-themeglow

Cómo enseña el widget que está escuchando. Glow pone una luz de color al pie de la ventana, spotlight atenúa la página, subtle solo cambia el botón.

data-accentEl nuestro

Su color, en cualquier forma que acepte CSS. Todo lo demás se deduce de ahí: este único atributo pasa el widget a su identidad visual.

data-backgroundEl acento

El fondo del botón, que es su color de acento salvo que diga otra cosa. Transparent le quita el fondo del todo.

data-foreground#fff

El color del icono y de la palabra sobre el botón. Blanco: correcto en un botón de color, invisible en uno claro.

data-radius999

Redondeo de las esquinas del botón, en píxeles. Cero es un cuadrado duro; omitido, se redondea al máximo: un círculo sin palabra, una pastilla con ella.

data-sizemedium

Lo grande que es el botón: small, medium o large. Un quinto más pequeño o más grande que el de serie, moviendo juntos icono, palabra y espacio.

data-positionright

En qué esquina va el botón: top (arriba a la derecha), right (abajo a la derecha) o left (abajo a la izquierda). Medido en el campo, o en la ventana.

data-anchorfield

El botón se sienta en la esquina inferior del campo que rellena. Póngalo en window para que flote en el borde de la pantalla - mejor con seis campos.

data-showalways

Si el botón está ahí desde el principio. Always es el botón de siempre, focus espera a que el cursor entre en el campo, never no añade nada.

data-devicealways

En qué aparatos se ofrece el botón: always, mobile o desktop. Móvil es señalar con el dedo: una tableta sí, un portátil táctil no.

data-appendtrue

Cada pulsación añade un párrafo nuevo debajo de lo que ya haya en el campo. Póngalo en false si cada pulsación debe reemplazar la respuesta anterior.

data-growfalse

Deja que un textarea crezca a medida que se llena, para no tener que recorrer hacia atrás un párrafo hablado. Apagado salvo que lo pida.

data-labelNinguno

Una palabra junto al micrófono, que el botón no lleva salvo que la pida. Auto usa nuestra redacción; otra cosa, hasta veinte caracteres.

data-languageSu página

Dos letras: una pista sobre lo que se va a hablar, y el idioma que habla el widget. Se lee del atributo lang de su página.

data-fontNinguno

Póngalo en inherit y el botón toma prestada la tipografía de la página. Omitido, usa una tipografía de sistema redondeada y discreta.

data-shortcuton

Ctrl+Mayús+Espacio arranca y para el micrófono desde cualquier punto de la página. Desactívelo si su página ya usa esa combinación.

data-soundoff

Un tono corto al arrancar la grabación: chime, pop o beep, o su propio archivo. Apagado salvo que lo pida - pero en un teléfono es lo que llega.

data-confettifalse

Una pequeña lluvia de papel cuando las palabras llegan al campo. Apagada salvo que la pida, y nunca tras una grabación que volvió vacía.

data-counterring

El tiempo restante: ring alrededor del botón, bar cruzando la ventana, o fill subiendo por dentro. Ámbar a los diez segundos, rojo a los cinco. Activo.

data-shadowfalse

Una sombra bajo el botón. Desactivada, porque cuánto flota un control sobre el papel ya lo ha decidido su sitio - true para claro sobre claro.

data-sessionNinguno

La dirección, en su propio sitio, que reparte los permisos de grabación, para que la key que gasta su cupo no aparezca en el código fuente.

Ajustes distintos en un campo

Todo lo de arriba se puede decir también en el propio campo, poniendo data-speech- delante del nombre. Al campo se le pregunta primero y la etiqueta script es el recurso de reserva, así que una excepción cuesta exactamente un atributo y todo lo que no mencione sigue obedeciendo a la página. Un formulario con una línea de asunto encima de un cuadro de mensaje puede rotularlos de otra forma, o tender la cuenta atrás a lo largo del borde del campo largo y dejarle un anillo al corto.

<input
  id="subject"
  data-speech-label="Dictate"
  data-speech-counter="ring"
/>

<textarea
  id="message"
  data-speech-anchor="field"
  data-speech-counter="bar"
  data-speech-grow="true"
></textarea>

El widget se viste para el campo en el que se está escribiendo en ese momento, así que esto sigue siendo un solo micrófono y no uno por cuadro. La key es el único atributo que no pinta nada aquí: una segunda key en una página es una segunda cuenta.

Merece la pena ponerlo en el propio campo

Dos atributos en su propio campo y no en la etiqueta script. Una transcripción es un párrafo que nadie ha corregido, así que deje encendido el corrector del navegador: el subrayado rojo es la forma más rápida de que quien acaba de hablar encuentre el único nombre que llegó mal, y corregirlo es un doble clic. La mayoría de los navegadores ya lo hacen en un textarea, pero no todos, y ponerlo de forma explícita lo hace cierto en todas partes. Autocapitalize mantiene las frases tecleadas a mano después, en un teléfono, en línea con las habladas.

<textarea
  id="message"
  spellcheck="true"
  autocapitalize="sentences"
  rows="4"
></textarea>

Aspecto

El widget vive en un shadow root, que es la única forma fiable de sobrevivir a que lo suelten dentro de una hoja de estilos escrita por otra persona: nada de lo suyo entra y nada de lo nuestro se escapa a su página. Esa misma protección haría imposible reestilizarlo, así que se han dejado dos puertas abiertas a propósito.

Custom properties

Ponga cualquiera de estas sobre el widget desde su propia hoja de estilos y gana a lo que pidiera la etiqueta script. Eso es lo que hace posible un modo oscuro, un color distinto por sección del sitio o una regla de impresión.

--as-accent
El único color del que se deduce todo lo demás.
--as-face
El fondo del propio botón, normalmente el acento.
--as-ink
El icono y la palabra sobre el botón, normalmente blancos.
--as-deep
La sombra bajo el botón, normalmente una versión más oscura del acento.
--as-lift
El botón bajo el puntero, normalmente una versión más clara del acento.
--as-radius
El redondeo de las esquinas del botón.
--as-scale
Lo grande que es el botón, como simple múltiplo de su tamaño normal - 0.8 es el ajuste small, 1.2 el large. Que sea una property y no solo un atributo es justamente la gracia: puede ponerse dentro de una media query, de modo que el botón tenga un tamaño en un ordenador y otro bajo un pulgar.
--as-font
La tipografía del botón y de la línea de estado.
--as-hot
El color de una grabación en marcha, que sigue a su color de acento salvo que usted diga otra cosa. Que hay una grabación en curso lo dicen el halo que late, la cuenta atrás y la palabra que la detiene, y no el color - así que ponga aquí el rojo si quiere recuperar la convención de siempre.
addspeech-microphone {
  --as-accent: #7c3aed;
  --as-radius: 12px;
}

@media (prefers-color-scheme: dark) {
  addspeech-microphone {
    --as-accent: #a78bfa;
  }
}

Parts

Para las cosas que una custom property no puede expresar: un botón más contundente, una línea de estado que encaje con sus propios avisos. Cada nombre de abajo es una promesa - cómo se llame un elemento dentro del widget puede cambiar en cualquier versión, lo que usted escriba en su hoja de estilos no.

::part(button)
El botón en sí, icono y palabra incluidos.
::part(label)
Solo la palabra del botón.
::part(icon)
Solo el micrófono.
::part(status)
La línea que dice qué está pasando - escuchando, transcribiendo, o qué ha fallado.
::part(countdown)
La pastilla bajo el botón que cuenta en palabras los últimos diez segundos.
::part(hint)
El pequeño recordatorio de teclado que se ve mientras hay una grabación en marcha.
::part(tip)
La burbujita sobre el botón que nombra el atajo de teclado, en un dispositivo con puntero.
::part(backdrop)
El resplandor, o la página atenuada detrás, según el tema.
::part(bar)
La línea de cuenta atrás por el borde inferior de la ventana, cuando ese es el contador que ha elegido.
::part(badge)
El pequeño enlace AddSpeech bajo el botón, en el plan gratuito.
addspeech-microphone::part(button) {
  box-shadow: none;
  font-weight: 600;
}

addspeech-microphone::part(status) {
  font-family: "Your Face", sans-serif;
}

El widget lleva además su propio estado encima, como atributos normales, de modo que puede dar estilo a un momento igual que a un elemento.

addspeech-microphone[data-state="listening"]::part(label) {
  text-transform: uppercase;
}

Todo lo que se mueve ya está apagado para quien pide menos movimiento en su sistema. Cada tema conserva la parte que dice que el micrófono está encendido y pierde solo la parte que se mueve. Este comportamiento no debería sobrescribirse.

Ejemplos

Cuatro resueltos. Cada uno es todo lo que tiene que haber en la página.

Cada uno está dibujado debajo. Son imágenes y no micrófonos en funcionamiento - una página que le pidiera cuatro permisos de grabación mientras lee sería una manera rara de defender una sola línea de script. El que funciona está en la portada.

Lo más pequeño que funciona

Un micrófono en la esquina inferior del campo que rellena, en nuestro color, sobre el único textarea de la página. Nada ajustado, nada elegido.

AddSpeech.vue

script.src = 'https://cdn.addspeech.com/widget.js';
script.dataset.key = 'as_pub_your_key_here';
Un pequeño micrófono redondo metido en la esquina inferior derecha de la casilla del mensaje.

En sus colores, al borde de la ventana

Flotando en el borde de la pantalla en lugar de estar dentro del campo, en su propio color, con las esquinas más rectas y su propia palabra junto al micrófono. Un botón que se sostiene solo es el único que puede llevar una palabra: anclado en un campo ya está junto a lo que rellena, y entonces la palabra sobra.

AddSpeech.vue

script.src = 'https://cdn.addspeech.com/widget.js';
script.dataset.key = 'as_pub_your_key_here';
script.dataset.anchor = 'window';
script.dataset.accent = '#7c3aed';
script.dataset.radius = '12';
script.dataset.label = 'Dictate';
script.dataset.sound = 'chime';
Una pastilla morada abajo a la derecha de la ventana, con Dictate junto a un micrófono, y el formulario detrás.

En un panel de control

Nada de la página se mueve salvo el botón que se ha pulsado, el atajo de teclado se le devuelve a la página, y la tipografía es la que ya estaba cargada.

AddSpeech.vue

script.src = 'https://cdn.addspeech.com/widget.js';
script.dataset.key = 'as_pub_your_key_here';
script.dataset.theme = 'subtle';
script.dataset.position = 'left';
script.dataset.font = 'inherit';
script.dataset.shortcut = 'off';
En plena grabación: el micrófono está abajo a la izquierda del campo con un halo suave, y el resto de la página está exactamente igual que antes.

Una sola respuesta hablada

Para una página cuyo único cometido es recoger lo que alguien dice: la página se retira, el micrófono se va al centro, y la casilla crece para acoger el párrafo que aterriza en ella.

AddSpeech.vue

script.src = 'https://cdn.addspeech.com/widget.js';
script.dataset.key = 'as_pub_your_key_here';
script.dataset.theme = 'spotlight';
script.dataset.grow = 'true';
En plena grabación: la página está atenuada detrás de un micrófono grande en el centro, con la respuesta ya dada asomando a través.

Poner nombre a sus formularios

Su página de cuenta desglosa el mes por formulario: cuántas respuestas recogió cada uno y cuánto duraba una respuesta típica. Eso solo se lee bien si sus formularios tienen nombre, y así es como lo consiguen.

Un formulario recibe el nombre del primero de estos que exista: un data-speech-name en el campo o en el formulario, el id del formulario, el name del formulario, el name del campo y, a falta de todo lo anterior, la dirección a la que el formulario envía. Así que casi todos los sitios reciben nombres razonables sin cambiar nada - pero lo de arriba de esa lista es lo que usted controla.

<form action="/customer-support/refund" data-speech-name="Refund Form">
  <textarea name="reason"></textarea>
</form>

Merece la pena cuando una misma página lleva varios formularios, o cuando el id es algo que ha generado un maquetador visual. «Devoluciones» y «Reclamación» le dicen algo de un vistazo; «form-7ab» no.

En ese último recurso solo se usa la ruta, y de ella solo sus dos últimos segmentos. La query string se descarta, y cualquier segmento que parezca un identificador - un número de pedido, un ULID, un hash - se sustituye por :id antes de guardarse. Una plantilla de formulario en ocho mil páginas de producto sigue siendo un formulario, y nunca se anota ningún número de pedido.

Nada de esto va sobre la persona que rellena el formulario. Lo que se registra es el nombre del formulario y la hora del día en el reloj del visitante, y las dos cosas describen su web y no a su visitante. Sigue sin haber columna para lo que alguien dijo.

Saber que se dictó

Si le resulta útil saber qué envíos se dictaron en lugar de escribirse, dele al formulario un campo oculto llamado _mic. El widget rellena ese campo; nunca lo añade. Un formulario que no lo lleve se comporta exactamente igual que hoy.

<form method="post" action="/contact">
  <textarea name="message"></textarea>

  <input type="hidden" name="_mic">
</form>

Se pone a true en el momento en que las palabras aterrizan en el campo, y en ningún momento anterior. Quien abre el micrófono y luego se lo piensa mejor, o cuya grabación vuelve vacía, envía el formulario con ese campo todavía en blanco - así que lo que le llega es «true» o nada en absoluto.

Lo que eso vale es real pero modesto. Dice que se grabó audio en este navegador, que se nos envió y que volvió con palabras dentro, y eso es bastante más trabajo del que se toma un script que rellena formularios. Con lo cual merece la pena sopesarlo, junto a lo que usted ya mire.

No es prueba de que una persona rellenara el formulario, y preferimos decirlo a venderlo como tal. El audio se puede meter en un micrófono desde un altavoz, o sintetizarse directamente por cualquiera dispuesto a dedicarle una tarde. Trátelo como trataría un tarro de miel: una señal barata que vale la pena tener, nunca lo que se interpone entre su formulario y el mundo.

Qué pasa con la grabación

  • No se guarda nada. El audio se transcribe y se descarta dentro de la misma request, siempre - no hay ningún cubo en el que aterrice primero, y no hay ningún ajuste que cambie eso.
  • Nada viaja al descubierto. Un navegador no le da un micrófono a una página que no esté en HTTPS, y la grabación nos llega por esa misma conexión cifrada - así que el audio no es legible en ningún punto del trayecto entre quien habla y la transcripción que vuelve.
  • Una key se rechaza por HTTP sin cifrar, aunque la dirección se añadiera así a la lista de dominios permitidos, y no hay ningún ajuste que lo desactive. La única excepción es localhost, donde el tráfico no sale de la máquina y donde la gente construye.
  • No se entrena nada con lo que pasa por aquí. Ni nuestros modelos ni los de nadie más.
  • El silencio no se cobra, y una request que falló por nuestro lado tampoco.
  • Si algo de eso cambia alguna vez, lo dirá esta página antes de que ocurra.

Recomendar a alguien

Cada cuenta lleva un enlace dentro. Cualquiera que se registre a través del suyo le da fills, y usted los asigna al mes que prefiera.

  • Se paga en fills, no en dinero. No hay factura que enviar, ni umbral que alcanzar, ni pago que esperar - la recompensa es el producto mismo, y sencillamente aparece en su cuenta.
  • Un registro gratuito vale 25 fills, un Freelancer 300 y una Agency 750. Son cantidades fijas y no un porcentaje, así que un cambio de precio nunca altera en silencio lo que vale una recomendación.
  • Un crédito se retiene un mes antes de poder gastarse, y la cuenta que lo generó tiene que haber creado un dominio y haber usado el micrófono de verdad al menos una vez. Vale igual para las altas gratuitas y las de pago; en un plan de pago la suscripción además tiene que haber sobrevivido el mes, porque un cargo con tarjeta todavía puede revertirse.
  • Usted decide en qué mes caen. Póngalos todos en el mes que viene si lo espera movido, o repártalos. Lo que un mes no gaste vuelve a su saldo cuando termina, así que quedarse largo no le cuesta nada.
  • El bote se gasta el último. Un sitio consume primero su propia asignación mensual y solo después echa mano de sus fills, de modo que asignarlos nunca puede abaratar un mes más de lo que ya estaba.
  • Seguir su enlace no pone ninguna cookie nueva. El código se recuerda en la sesión que ya existe y desaparece al cerrar el navegador - una memoria más corta que la habitual en el sector, y la única que podemos describir con honestidad. Usted ve cuánta gente vino y cuánta se registró; nunca ve quién.

Su enlace, sus clics y sus conversiones están en su página de cuenta, bajo Refer someone.

Cuando no funciona

El widget no pinta nunca un error en su página. Si algo va mal en la línea que ha pegado, se comunica en la consola del navegador, con nuestro nombre delante; por lo demás no se escribe nada. Una web en marcha no es el sitio para nuestros mensajes de error.

Un micrófono que no aparece en absoluto es casi siempre una de tres cosas: la página no está en HTTPS, el selector no coincide con nada, o la key no está permitida en este dominio. La consola dirá cuál.

Para cualquier otra cosa, escriba a [email protected]. Para cualquier cosa que parezca un problema de seguridad, escriba ahí primero y denos la oportunidad de arreglarlo antes de que sea público.