Documentatie

De plugin werkt meteen. Eén regel HTML zet een microfoon naast een veld, en al de rest op deze pagina is optioneel.

Snel installeren

Werk je met Claude, Codex of een andere agent? Kopieer en plak gewoon de volgende prompt en geef de installatie volledig uit handen.

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

Die instructies zijn een gewone pagina die je kunt openen en lezen voor je iets plakt. Hij zoekt zelf uit waar je project mee gebouwd is, vraagt het voor hij schrijft, en er gaat geen enkel stuk van je project naar ons toe.

Bewaar je diezelfde pagina als skill, op ~/.claude/skills/addspeech/SKILL.md, dan kun je hem op de volgende site bij naam aanroepen in plaats van opnieuw een link te plakken. Het is hoe dan ook hetzelfde bestand.

Installatie

Alles hieronder verandert mee met deze keuze: de installatiestap, de voorbeelden en hoe elke optie gespeld wordt. Hij wordt in deze browser onthouden, dus de volgende pagina die je opent staat meteen goed.

Eén script-tag vlak voor de sluitende body-tag. Geen buildstap, niets extra's in je lockfile en niets om bij te houden - en daarom is dit degene die we aanraden.

Plak dit vlak voor de sluitende body-tag van de pagina waar je formulier op staat. Dat is de hele installatie: niets te bouwen, niets te importeren, en geen library die je moet bijhouden.

<script
  src="https://cdn.addspeech.com/widget.js"
  data-key="as_pub_your_key_here"
  defer
></script>

Hij staat in de broncode van de pagina, net als een publishable key van Stripe of een key van Google Maps. Eén key per domein, en bij deze installatie is hij daar met opzet te lezen. Hij werkt alleen op de domeinen die in je account staan, dus wie hem naar zijn eigen site kopieert heeft er niets aan.

Wil je liever dat hij daar helemaal niet terechtkomt, dan kan je eigen server de key bewaren en elke pagina per opname een kortlevend token geven. Dat is de optie session hieronder, en dat is de installatie die we aanraden aan iedereen die een server heeft om hem op te zetten.

Meer is niet nodig. Zonder verdere instellingen vult de microfoon de textarea op de pagina - die waar iemand het laatst in stond te typen, als er meerdere zijn - dus een formulier met één berichtvak heeft helemaal geen target nodig. Voeg data-target toe, een CSS-selector zoals je aan querySelector zou geven, als het veld een input is in plaats van een textarea, of als maar één van meerdere textareas de jouwe is. Op een pagina waar niets matcht verschijnt de knop helemaal niet - dus de script-tag mag in de template staan die elke pagina gebruikt, en de microfoon duikt alleen op waar een formulier staat.

Elke plek in de pagina werkt, maar onderaan de body is de juiste: het script laadt zonder de pagina op te houden, en tegen de tijd dat het draait bestaat het veld waar het naar zoekt.

Vereisten

Een korte lijst, waaraan een doorsnee site al grotendeels voldoet.

De pagina moet over HTTPS geserveerd worden.

Browsers geven geen microfoon aan een pagina die dat niet doet, en daar kunnen wij van onze kant niets aan veranderen. Localhost is uitgezonderd, dus lokaal ontwikkelen werkt gewoon.

Het veld moet bestaan op het moment dat het script draait, en het moet een echte input of textarea zijn.

Iets bewerkbaars dat geen formulierveld is - een rich-text-editor opgebouwd uit divs - wordt nog niet ondersteund.

Het veld heeft plaats nodig voor een paar duizend tekens.

Mensen spreken in veel langere zinnen dan ze typen, en een minuut gewone spraak is al gauw duizend tekens - een maxlength van 255 of 500 eet dus stilletjes het einde van elke reactie op. Laat het attribuut weg of zet het op zo'n 3000. De widget kijkt naar het veld waar hij op gericht staat en meldt het in de console van de browser, en op je accountpagina, als hij er een tegenkomt die te klein is.

De kolom achter dat veld heeft diezelfde ruimte nodig.

Dit is de helft die meestal over het hoofd gezien wordt: een formulier dat in de browser negenhonderd tekens aanneemt, met een VARCHAR(255) erachter, gooit het einde weg bij de laatste stap, nadat iemand al gesproken heeft. Gebruik TEXT, of een VARCHAR die lang genoeg is om het te menen, en kijk elke validatieregel aan je serverkant na die de oude lengte nog spiegelt.

Elke browser van de laatste jaren.

Chrome, Edge, Firefox en Safari nemen allemaal op; op iOS werkt het in Safari en in elke andere browser op de telefoon, want dat is allemaal Safari eronder.

De bezoeker krijgt één keer de vraag om de microfoon, van zijn eigen browser, en het antwoord wordt onthouden voor jouw domein.

Wij zien dat venster nooit en kunnen er niet op vooruitlopen.

Zijn de reacties van een maand op, dan haalt de knop zichzelf van je pagina's tot het tegoed opnieuw ingaat.

Dat is opzet en geen storing. Je formulieren en tekstvakken blijven precies werken zoals ze deden voordat de widget toegevoegd werd, dus een bezoeker ziet geen microfoon in plaats van een microfoon die weigert. Het tegoed gaat opnieuw in op je factuurdatum en niet op de eerste van de maand, dus een abonnement dat op de twintigste begon, loopt vanaf de twintigste. Op je accountpagina zie je wat er verbruikt is en op welke datum hij terugkomt.

React, Vue, Livewire, WordPress, een gewone HTML-pagina - het maakt niet uit.

De widget schrijft in het veld zoals een typende bezoeker dat zou doen en vuurt daarna de input- en change-events af, dus een framework dat dat veld in de gaten houdt, ziet precies wat het verwacht.

Gebruik

Alles wat je de widget kunt vertellen, verteld als attributen op de script-tag. Alleen de key is verplicht; de rest heeft een standaardwaarde die zo gekozen is dat iets weglaten je iets verstandigs oplevert in plaats van iets kapots.

data-keyVerplicht

Je key, uit je account. Die zegt om welke site het gaat, en dus van wiens tegoed de opname afgaat.

data-targettextarea

Een CSS-selector voor het veld dat gevuld moet worden. Elke textarea op de pagina, tenzij je iets anders zegt.

data-themeglow

Hoe de widget laat zien dat hij luistert. Glow tilt een gekleurd licht op vanaf de onderrand, spotlight dimt de pagina, subtle alleen de knop.

data-accentDe onze

Jouw kleur, in elke vorm die CSS accepteert. Al het andere wordt eruit afgeleid, dus dit ene attribuut zet de widget in jouw huisstijl.

data-backgroundDe accentkleur

De achtergrond van de knop zelf, standaard je accentkleur. Transparent haalt hem helemaal weg.

data-foreground#fff

De kleur van het icoontje en het woord op de knop. Wit, wat klopt op een gekleurde knop en onzichtbaar is op een lichte.

data-radius999

Hoe rond de hoeken van de knop zijn, in pixels. Nul is een strak vierkant; weggelaten gaan ze zo rond als ze kunnen - een cirkel zonder woord, een pil met.

data-sizemedium

Hoe groot de knop is: small, medium of large. Een vijfde kleiner of groter dan standaard, waarbij icoontje, woord en ruimte samen meegaan.

data-positionright

In welke hoek de knop staat: top (rechtsboven), right (rechtsonder) of left (linksonder). Gemeten tegenover het veld, of tegenover het venster.

data-anchorfield

Waar de knop staat: in de hoek van het veld dat hij vult, of window om hem aan de rand van het scherm te laten zweven - beter bij zes velden.

data-showalways

Of de knop er meteen staat. Always is de knop die je altijd al had, focus wacht tot de cursor in het veld staat, never voegt helemaal niets toe.

data-devicealways

Op welke apparaten de knop wordt aangeboden: always, mobile of desktop. Mobiel is wijzen met een vinger: een tablet wel, een touchlaptop niet.

data-appendtrue

Elke druk zet een nieuwe alinea onder wat er al in het veld staat. Zet dit op false als een druk het vorige antwoord moet vervangen.

data-growfalse

Laat een textarea meegroeien met wat erin komt, zodat een ingesproken alinea niet teruggescrold hoeft te worden. Uit tenzij je erom vraagt.

data-labelGeen

Een woord naast de microfoon, dat de knop niet draagt tenzij je erom vraagt. Auto is onze eigen bewoording; iets anders mag tot twintig tekens.

data-languageJe pagina

Twee letters: een hint over wat er gesproken gaat worden, én de taal die de widget zelf spreekt. Wordt van het lang-attribuut van je pagina gelezen.

data-fontGeen

Zet hem op inherit en de knop leent het lettertype van de pagina waar hij op staat. Weggelaten gebruikt hij een rond systeemlettertype.

data-shortcuton

Ctrl+Shift+Space start en stopt de microfoon vanaf elke plek op de pagina. Zet hem uit als je pagina die combinatie al gebruikt.

data-soundoff

Een kort toontje zodra de opname start: chime, pop of beep, of je eigen bestand. Uit tenzij je erom vraagt - maar op een telefoon komt het aan.

data-confettifalse

Een kleine wolk papier zodra de woorden in het veld staan. Uit tenzij je erom vraagt, en nooit na een opname die leeg terugkwam.

data-counterring

De resterende tijd: ring om de knop, bar over de onderrand, of fill die vanbinnen oploopt. Oranje bij tien seconden, rood bij vijf. Standaard aan.

data-shadowfalse

Een schaduw onder de knop. Uit, want hoe ver een knop van het papier af zweeft heeft jouw site zelf allang beantwoord - true voor licht op licht.

data-sessionGeen

Het adres op je eigen site dat opnametoestemming uitdeelt, zodat de key die je tegoed opmaakt nergens in je paginabron staat.

Afwijkende instellingen op één veld

Alles hierboven kun je ook op een veld zelf zetten, met data-speech- voor de naam. Het veld wordt eerst gevraagd en de script-tag is de terugval, dus een uitzondering kost precies één attribuut en alles wat je niet noemt blijft de pagina volgen. Een formulier met een onderwerpregel boven een berichtvak kan die twee anders benoemen, of de aftelling langs de rand van het lange veld leggen en het korte veld een ring geven.

<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>

De widget kleedt zich naar het veld waar op dat moment in geschreven wordt, dus dit blijft één microfoon in plaats van één per vak. De key is het enige attribuut dat hier niet thuishoort: een tweede key op één pagina is een tweede account.

De moeite waard op het veld zelf

Twee attributen op je eigen veld in plaats van op de script-tag. Een transcriptie is een alinea die niemand heeft nagelezen, dus laat de spellingcontrole van de browser aanstaan: het rode kringeltje is de snelste manier waarop degene die net gesproken heeft die ene verkeerd verstane naam terugvindt, en verbeteren is een dubbelklik. De meeste browsers doen dit bij een textarea al vanzelf, maar niet allemaal, en het expliciet instellen maakt het overal waar. Autocapitalize houdt de zinnen die er daarna met de hand op een telefoon bij getypt worden in lijn met de ingesproken zinnen.

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

Vormgeving

De widget woont in een shadow root, want dat is de enige betrouwbare manier om te overleven in een stylesheet die door iemand anders geschreven is: niets van jou komt naar binnen, en niets van ons lekt naar jouw pagina. Diezelfde bescherming zou herstijlen onmogelijk maken, dus zijn er bewust twee deuren in opengelaten.

Custom properties

Zet een van deze vanuit je eigen stylesheet op de widget en het wint van wat de script-tag vroeg. Dat is wat een dark mode, een andere kleur per deel van de site of een print-regel überhaupt mogelijk maakt.

--as-accent
De ene kleur waar al het andere uit afgeleid wordt.
--as-face
De achtergrond van de knop zelf, normaal de accentkleur.
--as-ink
Het icoontje en het woord op de knop, normaal wit.
--as-deep
De schaduw onder de knop, normaal een donkerdere versie van de accentkleur.
--as-lift
De knop onder de muis, normaal een lichtere versie van de accentkleur.
--as-radius
Hoe rond de hoeken van de knop zijn.
--as-scale
Hoe groot de knop is, als een simpel veelvoud van zijn normale maat - 0.8 is de kleine stand, 1.2 de grote. Dat dit een property is en niet alleen een attribuut, is precies het punt: je kunt het binnen een media query zetten, zodat de knop op een desktop een andere maat heeft dan onder een duim.
--as-font
Het lettertype van de knop en de statusregel.
--as-hot
De kleur van een lopende opname, die je accent volgt tenzij je iets anders zegt. Dat er opgenomen wordt, wordt gezegd door de kloppende halo, de afteller en het woord dat hem stopt, en niet door de kleur - zet dit dus op rood als je de oude afspraak terug wilt.
addspeech-microphone {
  --as-accent: #7c3aed;
  --as-radius: 12px;
}

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

Parts

Voor de dingen die een custom property niet kan uitdrukken: een zwaardere knop, een statusregel die bij je eigen meldingen past. Elke naam hieronder is een belofte - hoe een element binnen de widget heet mag met elke release veranderen, wat jij in je stylesheet schrijft niet.

::part(button)
De knop zelf, inclusief het icoon en het woord erop.
::part(label)
Alleen het woord op de knop.
::part(icon)
Alleen de microfoon.
::part(status)
De regel die zegt wat er gebeurt - luisteren, uitschrijven, of wat er misging.
::part(countdown)
Het bolletje onder de knop dat de laatste tien seconden in woorden aftelt.
::part(hint)
Het kleine toetsenbordgeheugensteuntje dat tijdens een opname te zien is.
::part(tip)
Het bolletje boven de knop dat de sneltoets noemt, op een apparaat met een muisaanwijzer.
::part(backdrop)
De gloed, of de gedimde pagina erachter, afhankelijk van het thema.
::part(bar)
Het aftellijntje over de onderrand van het venster, als dat de afteller is die je koos.
::part(badge)
Het kleine AddSpeech-linkje onder de knop, op het gratis plan.
addspeech-microphone::part(button) {
  box-shadow: none;
  font-weight: 600;
}

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

De widget draagt zijn eigen toestand ook op zichzelf, als gewone attributen, zodat je net zo goed een moment kunt vormgeven als een element.

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

Alles wat beweegt staat al uit voor wie zijn systeem om minder beweging vraagt. Elk thema houdt het deel dat zegt dat de microfoon aan staat en verliest alleen het deel dat beweegt. Dit gedrag hoort niet overschreven te worden.

Voorbeelden

Vier uitgewerkte. Elk daarvan is alles wat er op de pagina hoeft te staan.

Van elk staat eronder getekend hoe het eruitziet. Dat zijn plaatjes en geen werkende microfoons - een pagina die je vier keer om opnametoestemming vraagt terwijl je zit te lezen, is een vreemde manier om één regel script te verdedigen. De werkende staat op de voorpagina.

Het kleinste dat werkt

Een microfoon in de onderhoek van het veld dat hij vult, in onze kleur, op de enige textarea van de pagina. Niets ingesteld, niets gekozen.

<script
  src="https://cdn.addspeech.com/widget.js"
  data-key="as_pub_your_key_here"
  defer
></script>
Een klein rond microfoontje in de rechteronderhoek van het berichtveld.

In jouw kleuren, aan de rand van het venster

Zwevend aan de rand van het scherm in plaats van in het veld, in je eigen kleur, met strakkere hoeken en je eigen woord naast de microfoon. Een knop die op zichzelf staat is de enige die een woord kan dragen: in een veld staat hij al naast wat hij invult, en dan gaat dat woord eraf.

<script
  src="https://cdn.addspeech.com/widget.js"
  data-key="as_pub_your_key_here"
  data-anchor="window"
  data-accent="#7c3aed"
  data-radius="12"
  data-label="Dictate"
  data-sound="chime"
  defer
></script>
Een paarse pil rechtsonder in het venster, met Dictate naast een microfoon, en het formulier erachter.

Op een dashboard

Niets op de pagina beweegt behalve de knop waar op gedrukt is, de sneltoets wordt teruggegeven aan de pagina, en het lettertype is dat wat er al geladen was.

<script
  src="https://cdn.addspeech.com/widget.js"
  data-key="as_pub_your_key_here"
  data-theme="subtle"
  data-position="left"
  data-font="inherit"
  data-shortcut="off"
  defer
></script>
Middenin een opname: de microfoon staat linksonder in het veld met een zachte halo eromheen, en de rest van de pagina is precies zoals hij was.

Eén gesproken reactie

Voor een pagina die alleen bestaat om op te halen wat iemand zegt: de pagina stapt terug, de microfoon komt in het midden, en het vak groeit mee met de alinea die erin belandt.

<script
  src="https://cdn.addspeech.com/widget.js"
  data-key="as_pub_your_key_here"
  data-theme="spotlight"
  data-grow="true"
  defer
></script>
Middenin een opname: de pagina is gedimd achter een grote microfoon in het midden, met het al gegeven antwoord dat erdoorheen schemert.

Je formulieren een naam geven

Je accountpagina splitst de maand op per formulier: hoeveel reacties elk formulier ophaalde, en hoe lang een doorsnee reactie duurde. Dat leest alleen prettig als je formulieren een naam hebben, en zo krijgen ze er een.

Een formulier krijgt de naam van het eerste dat bestaat: een data-speech-name op het veld of op het formulier, de id van het formulier, de name van het formulier, de name van het veld, en als niets daarvan bestaat het adres waar het formulier naartoe post. De meeste sites krijgen dus verstandige namen zonder iets te veranderen - maar bovenaan die lijst staat wat jij zelf in de hand hebt.

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

De moeite waard zodra één pagina meerdere formulieren draagt, of wanneer de id iets is dat een paginabouwer verzonnen heeft. "Retour" en "Klacht" zeggen je in één oogopslag iets; "form-7ab" niet.

Bij dat laatste redmiddel wordt alleen het pad gebruikt, en daarvan alleen de laatste twee stukken. De query string gaat eraf, en elk stuk dat op een kenmerk lijkt - een bestelnummer, een ULID, een hash - wordt vervangen door :id vóór het opgeslagen wordt. Eén formuliersjabloon op achtduizend productpagina's blijft één formulier, en er wordt nooit een bestelnummer opgeschreven.

Niets hiervan gaat over de persoon die het formulier invult. Wat bewaard wordt is de naam van het formulier en het uur van de dag op de klok van de bezoeker, en allebei beschrijven ze jouw website en niet jouw bezoeker. Er is nog steeds geen kolom voor wat iemand gezegd heeft.

Weten dat het ingesproken is

Als het je van pas komt om te weten welke inzendingen ingesproken zijn in plaats van getypt, geef het formulier dan een verborgen veld met de naam _mic. De widget vult dat veld in; hij voegt het nooit toe. Een formulier zonder dat veld gedraagt zich precies zoals het zich vandaag gedraagt.

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

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

Het wordt op true gezet op het moment dat de woorden in het veld landen, en op geen enkel moment daarvóór. Wie de microfoon opent en er zich toch van bedenkt, of wiens opname leeg terugkomt, verstuurt het formulier met dat veld nog leeg - wat er bij jou aankomt is dus "true" of helemaal niets.

Wat dat waard is, is echt maar bescheiden. Het zegt dat er in deze browser audio is opgenomen, naar ons is gestuurd, en met woorden erin is teruggekomen, en dat is heel wat meer moeite dan een script dat formulieren invult zich getroost. Daarmee is het het wegen waard, naast waar je toch al naar kijkt.

Het is geen bewijs dat een mens het formulier heeft ingevuld, en we zeggen dat liever dan het als zodanig te verkopen. Audio kan vanaf een luidspreker een microfoon in gespeeld worden, of ronduit gemaakt worden door iemand die er een middag in wil steken. Behandel het zoals je een honeypot behandelt: een goedkoop signaal dat de moeite waard is, nooit het ding dat tussen jouw formulier en de wereld in staat.

Wat er met de opname gebeurt

  • Er wordt niets bewaard. De audio wordt binnen hetzelfde request uitgeschreven en weggegooid, elke keer - er is geen emmer waar het eerst in landt, en geen instelling die daar iets aan verandert.
  • Er gaat niets onversleuteld over de lijn. Een browser geeft een pagina die niet op HTTPS staat sowieso geen microfoon, en de opname bereikt ons over diezelfde versleutelde verbinding - de audio is dus nergens onderweg mee te lezen, tussen degene die spreekt en de tekst die terugkomt.
  • Een key wordt geweigerd over gewone HTTP, ook als het adres op die manier aan de lijst met toegestane domeinen is toegevoegd, en er is geen instelling die dat uitzet. De enige uitzondering is localhost, waar het verkeer de machine niet verlaat en waar mensen bouwen.
  • Er wordt niets getraind op wat hier langskomt. Onze modellen niet, en die van een ander ook niet.
  • Stilte wordt niet in rekening gebracht, en een request dat aan onze kant misging ook niet.
  • Verandert daar ooit iets aan, dan staat het op deze pagina voordat het gebeurt.

Iemand doorverwijzen

In elk account zit een linkje. Iedereen die zich via jouw link aanmeldt, levert je fills op, en die mag je toekennen aan de maand die jou uitkomt.

  • Uitbetaald in fills, niet in geld. Geen factuur die je moet sturen, geen drempel die je eerst moet halen en geen uitbetaling om op te wachten - de beloning is het product zelf, en die staat gewoon in je account.
  • Een gratis aanmelding is 25 fills waard, een Freelancer 300 en een Agency 750. Dat zijn vaste aantallen en geen percentage, zodat een prijswijziging nooit stilletjes verandert wat een doorverwijzing waard is.
  • Een tegoed wordt een maand vastgehouden voordat het uitgegeven kan worden, en het account dat het opleverde moet een domein hebben aangemaakt en de microfoon minstens één keer echt gebruikt hebben. Dat geldt voor gratis én betaalde aanmeldingen; bij een betaald plan moet het abonnement die maand bovendien overleefd hebben, want een kaartbetaling kan nog teruggedraaid worden.
  • Jij bepaalt in welke maand ze landen. Zet ze allemaal op volgende maand als je een drukke verwacht, of verdeel ze. Wat een maand niet opmaakt, komt aan het eind weer bij je saldo, dus te hoog gokken kost je niets.
  • De pot gaat er als laatste aan. Een site verbruikt eerst zijn eigen maandtegoed en grijpt pas daarna naar je fills, waardoor toekennen een maand nooit goedkoper kan maken dan hij al was.
  • Je link volgen zet geen nieuw cookie. De code wordt onthouden in de sessie die er toch al is en is weg zodra de browser dichtgaat - een kortere herinnering dan wat in deze branche gebruikelijk is, en de enige die we eerlijk kunnen opschrijven. Je ziet hoeveel mensen er kwamen en hoeveel zich aanmeldden; je ziet nooit wie.

Je link, je clicks en je conversions staan op je accountpagina, onder Refer someone.

Als het niet werkt

De widget tekent nooit een foutmelding op jouw pagina. Klopt er iets niet aan de regel die je geplakt hebt, dan wordt dat in de console van de browser gemeld, met onze naam ervoor; verder wordt er niets geschreven. Een draaiende website is niet de plek voor onze foutmeldingen.

Een microfoon die helemaal niet verschijnt is bijna altijd een van drie dingen: de pagina staat niet op HTTPS, de selector past nergens op, of de key mag niet op dit domein. De console zegt welke van de drie.

Al het andere: schrijf naar [email protected]. Alles wat op een beveiligingsprobleem lijkt: schrijf daar eerst naartoe en geef ons de kans het op te lossen voordat het openbaar is.