Torna al blog
19 ottobre 2026Sergei Solod24 min di lettura

Come aggiungere la trascrizione vocale privata a un sito web con Whisper e WebAssembly

Realizzate una dettatura interamente lato client con Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet e WebAssembly. Scoprite come ottimizzare le prestazioni sui dispositivi mobili e risolvere gli errori dell’ambiente di esecuzione, dei worker e del tokenizer che impediscono il funzionamento del riconoscimento vocale.

Riconoscimento vocaleWhisperWebAssemblyAudioWorkletJavaScript

Aggiungere un pulsante per il microfono a un sito web sembra semplice: acquisire la voce dell’utente, riconoscere le parole e inserirle in un campo di testo. In pratica, ottenere un funzionamento affidabile sui browser desktop e mobili è molto più complicato, soprattutto quando il riconoscimento vocale deve avvenire interamente in locale, senza caricare l’audio del microfono su un server.

La difficoltà non sta soltanto nello scegliere un modello di riconoscimento vocale. Un’implementazione funzionante deve gestire correttamente i permessi del microfono, l’acquisizione audio, il ricampionamento, i worker del browser, WebAssembly, l’inizializzazione dei modelli ONNX, la tokenizzazione, la cache e l’integrazione con il campo di testo dell’applicazione.

Questo articolo spiega come realizzare una funzionalità pratica di trascrizione vocale che tuteli la riservatezza, utilizzando OpenAI Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet e un Web Worker dedicato. Esamina anche diversi problemi di integrazione riscontrati nella pratica, capaci di lasciare il pulsante del microfono bloccato sul caricamento anche quando i file del modello sono stati scaricati correttamente.

L’obiettivo è semplice: chi visita il sito preme il pulsante del microfono, parla normalmente e vede comparire il testo riconosciuto in un campo di input già presente. Può rileggere o modificare il risultato prima di inviarlo. La registrazione del microfono non deve mai uscire dal browser.

Che cosa significa riconoscimento vocale interamente lato client

Esistono due approcci sostanzialmente diversi per aggiungere l’input vocale a un sito web.

Il primo utilizza un servizio di riconoscimento vocale. Il browser acquisisce l’audio, lo invia a un servizio remoto e riceve il testo trascritto. Questo può semplificare l’implementazione, ma introduce dipendenza dalla rete, questioni di riservatezza, disponibilità del fornitore e possibili costi legati all’utilizzo.

Il secondo approccio esegue l’inferenza direttamente nel browser. Il sito scarica un modello di riconoscimento vocale e l’ambiente necessario a eseguirlo. Il browser acquisisce l’audio dal microfono, lo converte in campioni numerici, esegue il modello in locale e genera la trascrizione senza ricorrere a un server di riconoscimento vocale.

Questo articolo si concentra esclusivamente sul secondo approccio.

È importante non confondere il normale riconoscimento vocale del browser con quello sicuramente eseguito in locale. La Web Speech API può utilizzare un motore di riconoscimento su server. Alcune implementazioni più recenti offrono un’opzione processLocally , ma il supporto varia a seconda del browser e del sistema operativo. Se è indispensabile l’elaborazione esclusivamente locale, l’interfaccia generica SpeechRecognition non deve essere utilizzata come soluzione di ripiego automatica.

L’applicazione deve invece controllare l’intera pipeline di inferenza.

Microfono
    |
    v
AudioWorklet
    |
    v
Audio PCM Float32
    |
    v
Web Worker dedicato
    |
    v
Ricampionamento a 16 kHz
    |
    v
Whisper + ONNX Runtime Web
    |
    v
Tokenizer e decodifica del testo
    |
    v
Stato del campo React

Il sito continua a distribuire JavaScript, WebAssembly, file di configurazione e pesi del modello: si tratta di normali download statici. La differenza è che l’audio del microfono viene elaborato in locale invece di essere caricato su un server per il riconoscimento. Se l’utente invia successivamente il testo ottenuto tramite un normale modulo, questa è un’azione distinta dell’applicazione.

Scegliere un modello pratico: Whisper Tiny o Base

Whisper è una famiglia di modelli di riconoscimento vocale multilingue. La possibilità di riconoscere molte lingue con un unico modello lo rende particolarmente utile per i siti multilingue.

Per i messaggi vocali brevi, due modelli offrono un buon equilibrio tra qualità del riconoscimento e consumo di risorse del browser: Whisper Tiny e Whisper Base.

ModelloDimensioni approssimative dei pesi ONNX Q8 selezionatiImpiego consigliato
Whisper Tiny41 MBScelta prudente per dispositivi mobili e con risorse limitate
Whisper Base77 MBQualità superiore su dispositivi adeguatamente potenti e verificati

Queste dimensioni si riferiscono ai file ONNX dell’encoder e del decoder selezionati nelle esportazioni esaminate. Non comprendono tutti i file di supporto e non vanno confuse con il picco di memoria utilizzata dal browser.

Whisper Tiny è interessante perché si scarica più rapidamente, richiede normalmente meno risorse di calcolo ed è più adatto ad ambienti mobili dalle capacità incerte. Whisper Base offre generalmente una qualità di riconoscimento migliore, soprattutto per alcune lingue in cui i modelli più piccoli commettono più errori.

La scelta del modello non dovrebbe basarsi soltanto sulla RAM fisica. Il browser potrebbe avere a disposizione molta meno memoria di quella indicata nelle specifiche del dispositivo. Contano anche la memoria già occupata dalla pagina, le allocazioni WebAssembly, i tensori temporanei e la pressione sulla memoria del sistema operativo.

Un approccio prudente consiste nell’iniziare con Tiny sui dispositivi mobili dalle capacità sconosciute, riservare Base alle categorie di dispositivi che soddisfano i requisiti e ripiegare su Tiny se l’inizializzazione di Base non riesce.

Prima di caricare Tiny, occorre liberare l’istanza di Base che non è riuscita a inizializzarsi. Mantenere entrambi i modelli in memoria vanifica lo scopo del ripiego.

Perché WebAssembly è una scelta sensata come soluzione predefinita in produzione

ONNX Runtime Web consente di eseguire reti neurali nei browser. Per questo carico di lavoro, WebAssembly offre una modalità pratica di inferenza su CPU nei principali browser desktop e mobili.

WebGPU può accelerare i modelli supportati su hardware adatto, ma la sua disponibilità non equivale a una pipeline di riconoscimento vocale funzionante. Il fatto che un browser esponga navigator.gpu non garantisce che ONNX Runtime supporti la piattaforma, che il particolare grafo Whisper venga inizializzato correttamente o che inferenze ripetute non causino problemi di risorse.

Un’implementazione prudente può quindi utilizzare WASM come ambiente di esecuzione in produzione e valutare WebGPU separatamente, dopo test di compatibilità, accuratezza, memoria e sessioni ripetute.

Nella prima implementazione, usare WASM a thread singolo evita inoltre di rendere l’intero sito dipendente dall’isolamento tra origini e da SharedArrayBuffer.

env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;

Eseguire l’inferenza in un worker dedicato mantiene i calcoli più onerosi fuori dal thread JavaScript principale. Il campo di input, la tastiera virtuale, lo scorrimento e l’interfaccia restano reattivi durante l’elaborazione dell’audio.

Escludere del tutto il riconoscimento vocale dal caricamento iniziale della pagina

La funzionalità del microfono non dovrebbe imporre un consumo significativo di rete e memoria ai visitatori che non la utilizzano mai.

Non importate Transformers.js in anticipo nel bundle iniziale dell’applicazione. Non inizializzate ONNX Runtime all’apertura della pagina e non scaricate automaticamente i pesi del modello durante i periodi di inattività.

Caricate invece i componenti di riconoscimento soltanto quando l’utente preme esplicitamente il pulsante del microfono.

Una sequenza utile al primo utilizzo è la seguente:

  1. Ricevere il clic sul pulsante del microfono.
  2. Richiedere il permesso di usare il microfono tramite getUserMedia().
  3. Se il permesso viene negato, mostrare un errore senza scaricare il modello.
  4. Se viene concesso, rilasciare il flusso temporaneo del microfono mentre si prepara il modello.
  5. Inizializzare dinamicamente il worker dedicato e caricare il modello locale selezionato.
  6. Attendere che il worker confermi che la pipeline di riconoscimento è pronta.
  7. Avviare automaticamente la registrazione se l’utente non ha annullato l’operazione.
  8. Mostrare uno stato di ascolto comprensibile.

La richiesta del permesso deve essere collegata all’interazione dell’utente. L’applicazione deve anche gestire le operazioni annullate e le limitazioni di acquisizione specifiche dei browser, senza presumere che sia sempre possibile riavviare la registrazione senza un’altra interazione.

La preparazione del modello dovrebbe essere accompagnata da uno stato visibile, per esempio Preparazione dell’input vocale. Un indicatore di caricamento privo di un passaggio di stato comprensibile può far sembrare guasto il microfono anche se il modello è stato scaricato correttamente.

Dopo l’inizializzazione, il worker può restare disponibile per un breve periodo per un’altra dettatura. Un timeout di inattività di circa un minuto è un punto di partenza ragionevole. Trascorso quel tempo, terminate il worker e liberate il modello dalla memoria. I file del modello conservati nella cache del browser possono restare disponibili per le visite successive.

Acquisire l’audio grezzo con AudioWorklet

Whisper richiede campioni audio numerici. MediaRecorder è ottimo per registrare file audio, ma usarlo per il riconoscimento locale potrebbe introdurre un passaggio non necessario di codifica e successiva decodifica.

Il percorso di elaborazione più semplice è questo:

getUserMedia()
    |
    v
AudioContext
    |
    v
AudioWorklet
    |
    v
Buffer PCM Float32
    |
    v
Worker dedicato

AudioWorklet esegue il codice di elaborazione audio al di fuori del thread JavaScript principale dell’applicazione. Può acquisire campioni PCM, ridurre a mono un ingresso multicanale, calcolare semplici misure del volume e trasferire al worker buffer contenenti i campioni accumulati.

Ecco un esempio semplificato di inizializzazione del microfono:

const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    channelCount: { ideal: 1 },
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
  },
});

const context = new AudioContext();

await context.audioWorklet.addModule(
  '/assets/speech/pcm-worklet.js'
);

const source = context.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(
  context,
  'pcm-capture'
);

source.connect(processor);

// Collegare il worklet a un’uscita non udibile
// se necessario per mantenere attivo il grafo di elaborazione audio.

I vincoli specificati per il microfono esprimono preferenze, non garanzie. I browser possono applicare algoritmi diversi per la cancellazione dell’eco, la riduzione del rumore e il controllo automatico del guadagno.

Un dettaglio importante è che la frequenza di campionamento del microfono non è necessariamente di 16 kHz. Molti contesti audio utilizzano 44.1 kHz o 48 kHz. Whisper richiede un ingresso a 16 kHz, quindi i campioni acquisiti devono essere ricampionati correttamente.

Scartare semplicemente un campione ogni due o tre non è, in generale, una soluzione corretta. Un ricampionatore passa-basso adatto, per esempio un’implementazione sinc finestrata, riduce l’aliasing e conserva le frequenze utili della voce quando si abbassa la frequenza di campionamento.

Eseguite il ricampionamento nel worker dedicato, non nella callback in tempo reale di AudioWorklet.

Trasferire l’audio in modo efficiente tra i thread del browser

AudioWorklet può produrre molti piccoli blocchi audio. Inviare ogni quantum di elaborazione audio come messaggio separato può creare un sovraccarico non necessario.

Una strategia pratica consiste nell’accumulare brevi gruppi di campioni PCM e trasferire ciascun gruppo come ArrayBuffer.

worker.postMessage(
  {
    type: 'audio-chunk',
    buffer,
  },
  [buffer]
);

L’elenco dei trasferimenti è importante: trasferisce la proprietà dell’ArrayBuffer anziché crearne inutilmente una copia completa.

Il worker può aggiungere i blocchi a un accumulatore audio e combinarli quando la registrazione termina.

È utile anche tenere traccia della frequenza di campionamento effettiva durante l’acquisizione, del numero di campioni raccolti e della durata della registrazione. Questi metadati aiutano a diagnosticare registrazioni silenziose senza registrare dati audio sensibili.

Rilevare le pause senza aggiungere un’altra rete neurale

Per dettare brevi messaggi di testo non è necessariamente richiesto un rilevatore neurale dell’attività vocale.

Un rilevatore RMS adattivo può fornire una stima leggera dell’attività vocale. Il valore RMS misura l’ampiezza del segnale, permettendo all’applicazione di stimare quando una persona che stava parlando ha smesso di farlo.

L’interfaccia dovrebbe consentire sempre l’interruzione manuale con un secondo clic sul microfono. Il rilevamento automatico del silenzio è una comodità, non un requisito per ottenere un riconoscimento corretto.

Tra i valori iniziali ragionevoli per dettature brevi figurano:

ParametroValore iniziale
Intervallo di analisi20 ms
Calibrazione del rumore300 ms
Conferma della presenza di voce140 ms
Silenzio prima dell’arresto automatico1,4 secondi
Timeout in assenza di voce8 secondi
Durata massima della registrazione30 secondi

Sono valori di partenza per la configurazione, non soglie ottimali valide in ogni situazione. Una voce debole, la musica di sottofondo e l’elaborazione del rumore del microfono possono influenzare le misure RMS.

È preferibile lasciare all’utente la possibilità di fermare manualmente la registrazione anziché rischiare di tagliare automaticamente le ultime parole di una frase.

Per l’inserimento di testi brevi, una trascrizione in un’unica esecuzione è generalmente più semplice che chiamare Whisper ripetutamente per ottenere risultati provvisori. Registrate la frase, fermate l’acquisizione, eseguite il modello una volta e inserite la trascrizione definitiva.

La pipeline completa di Whisper richiede più di un modello ONNX

Questo è uno dei dettagli più importanti dell’implementazione.

L’inferenza con Whisper non consiste semplicemente nel chiamare un encoder ONNX e poi un decoder ONNX. Una pipeline completa di riconoscimento vocale automatico richiede anche l’estrazione delle caratteristiche audio, la tokenizzazione, la configurazione della generazione e la decodifica dei token previsti in testo leggibile.

La sequenza completa di elaborazione è questa:

Audio PCM grezzo del microfono
        |
        v
Ricampionamento a 16 kHz
        |
        v
Estrattore di caratteristiche Whisper
        |
        v
Encoder ONNX
        |
        v
Decoder ONNX e generazione dei token
        |
        v
Tokenizer Whisper
        |
        v
Testo riconosciuto

L’inizializzazione riuscita di una sessione ONNX dimostra soltanto che è possibile caricare il grafo del modello. Non dimostra che l’intera pipeline di riconoscimento funzioni.

In un’integrazione con Transformers.js sottoposta a test, le sessioni del modello potevano essere inizializzate, ma la trascrizione continuava a fallire perché la pipeline costruita localmente non aveva il processore e il tokenizer necessari configurati correttamente.

L’implementazione compatibile inizializzava esplicitamente questi componenti usando i metadati locali del modello.

Il seguente esempio illustra in forma ridotta quell’approccio specifico per la versione utilizzata: non è un worker autonomo completo.

import {
  WhisperTokenizer,
  WhisperFeatureExtractor,
  WhisperProcessor,
} from '@huggingface/transformers';

// Questi valori provengono dai file di configurazione locali del modello.
const tokenizer = new WhisperTokenizer(
  tokenizerJson,
  tokenizerConfig
);

const featureExtractor = new WhisperFeatureExtractor(
  preprocessorConfig
);

const processor = new WhisperProcessor(
  {},
  {
    tokenizer,
    feature_extractor: featureExtractor,
  },
  ''
);

// Nell’integrazione interessata, specifica per quella versione:
recognizer.tokenizer = tokenizer;
recognizer.processor = processor;

Le firme esatte dei costruttori e il metodo di integrazione devono essere verificati rispetto alla versione di Transformers.js installata. Alcune versioni ed esportazioni dei modelli possono creare automaticamente questi componenti; la costruzione manuale non va considerata un requisito universale.

Una volta disponibile la pipeline completa, è possibile avviare la trascrizione di un audio breve specificando esplicitamente lingua e attività:

const result = await recognizer(audio16k, {
  language: 'en',
  task: 'transcribe',
  return_timestamps: false,
});

Quando l’applicazione conosce già la lingua dell’utente, indicarla esplicitamente evita un rilevamento linguistico non necessario e può rendere più prevedibile il risultato delle registrazioni brevi.

Fissare versioni compatibili di Transformers.js e ONNX Runtime

Una seconda importante modalità di errore riguarda la relazione tra Transformers.js e ONNX Runtime Web.

Transformers.js fornisce la pipeline di riconoscimento di livello superiore, mentre ONNX Runtime Web esegue i grafi delle reti neurali. Questi pacchetti devono essere compatibili tra loro e con le precise esportazioni ONNX utilizzate.

Nell’implementazione esaminata, una versione di sviluppo di ONNX Runtime Web inizializzava l’encoder Whisper, ma falliva durante la creazione della sessione del decoder quantizzato. L’errore riguardava l’assenza di un fattore di scala della quantizzazione in un’operazione DequantizeLinear .

Non era un problema di permessi del microfono, di download o di formato audio. Il browser riusciva a ricevere i file del modello, ma l’ambiente di esecuzione non riusciva a inizializzare uno dei grafi necessari.

La verifica diretta di encoder e decoder con una versione stabile e bloccata di ONNX Runtime Web ha risolto quella specifica incompatibilità.

La verifica di compatibilità è stata particolarmente utile perché ha permesso di distinguere due affermazioni:

  • Il file del modello è stato scaricato correttamente.
  • L’ambiente di esecuzione ha creato correttamente una sessione di inferenza utilizzabile a partire dal file del modello.

Non sono la stessa cosa.

Per le esportazioni dei modelli sottoposte a test, una combinazione compatibile utilizzava Transformers.js 4.2.0 insieme a ONNX Runtime Web 1.24.3. Una versione di sviluppo più recente dell’ambiente di esecuzione falliva sul grafo del decoder quantizzato. Si tratta di un’osservazione specifica di quelle versioni, non della prova che gli ambienti di esecuzione più vecchi siano sempre migliori.

Quando si sostituisce la versione di una dipendenza transitiva dell’ambiente di esecuzione, occorre fissare la versione esatta nel file di blocco delle dipendenze e documentare il motivo della sostituzione. Un aggiornamento dell’ambiente di esecuzione dovrebbe richiedere un test di regressione completo dell’inferenza nel browser, non soltanto l’installazione riuscita del pacchetto.

Controllare il formato del bundle del worker prima di cercare errori in Whisper

Un Web Worker JavaScript può essere creato utilizzando formati di modulo diversi.

Un errore di integrazione si è verificato perché l’applicazione avviava il worker come modulo ES, mentre il bundle generato per quel worker usava il meccanismo dei worker classici importScripts().

Questi modelli di esecuzione non sono intercambiabili. Un worker di tipo modulo non può semplicemente eseguire codice destinato esclusivamente ai worker classici.

La lezione pratica è scegliere il costruttore Worker in base al bundle effettivamente generato, anziché copiare un’opzione del costruttore da una guida generica.

Per esempio, le seguenti opzioni non sono automaticamente equivalenti:

// Worker di tipo modulo: richiede un bundle di moduli compatibile.
new Worker(workerUrl, { type: 'module' });

// Worker classico: richiede un bundle classico compatibile.
new Worker(workerUrl);

La scelta corretta dipende dal bundler e dal JavaScript prodotto.

Per diagnosticare un errore di inizializzazione del worker, esaminate la console del browser, il file generato per il worker e il pannello Rete. Stabilite se l’errore si verifica prima ancora che inizi l’inizializzazione di ONNX Runtime.

Alcune librerie incluse in un bundle calcolano inoltre gli URL rispetto alla posizione del proprio modulo. Quando vengono integrate nei worker, verificate i percorsi effettivi delle risorse dell’ambiente di esecuzione, senza presumere che gli URL relativi al pacchetto originale continuino a funzionare.

Servire i moduli WASM e JavaScript con i tipi MIME corretti

Anche file JavaScript e modelli ONNX perfettamente compatibili possono non funzionare se il server web restituisce intestazioni di risposta errate.

Un problema concreto riguardava un modulo JavaScript con estensione .mjs servito come application/octet-stream. Il browser rifiutava il modulo perché il tipo di contenuto della risposta non era adatto all’esecuzione di moduli JavaScript.

I tipi di risposta attesi sono in genere i seguenti:

Estensione del fileContent-Type
.mjsapplication/javascript
.wasmapplication/wasm

Controllate la risposta effettiva in produzione, non soltanto il file di configurazione del server.

curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasm

Gli URL precedenti sono puramente indicativi. Utilizzate i veri nomi dei file versionati della distribuzione dell’ambiente di esecuzione scelta.

Uno stato HTTP corretto non basta. Il browser può comunque rifiutare un file a causa del tipo MIME, della Content Security Policy, della modalità di esecuzione del modulo o di un URL errato della risorsa.

Ricordate che la stessa richiesta può riuscire quando viene aperta direttamente in una scheda del browser, ma fallire se la risorsa viene caricata come modulo eseguibile o dipendenza WebAssembly.

Usare URL immutabili senza lasciare gli utenti bloccati su file difettosi dell’ambiente di esecuzione

I file dei modelli Whisper sono abbastanza grandi da rendere importante la cache del browser.

I percorsi immutabili e versionati consentono al browser di riutilizzare i file invariati dei modelli e dell’ambiente di esecuzione, senza doverli scaricare ogni volta che l’utente parla.

Una tipica politica di cache è:

Cache-Control: public, max-age=31536000, immutable

Questo comporta però un requisito per il rilascio: una volta dichiarato immutabile un URL, il suo contenuto non dovrebbe essere sostituito senza cambiare indirizzo.

Se deve cambiare il binario dell’ambiente di esecuzione, pubblicatelo con un nuovo URL versionato.

/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/

Cambiare il percorso dell’ambiente di esecuzione non è una modifica puramente estetica. Se l’applicazione riutilizza un URL immutabile dopo averne cambiato il contenuto, il browser può continuare a usare un vecchio binario WebAssembly incompatibile presente nella cache.

È utile anche mantenere i file JavaScript di collegamento e i file WebAssembly della stessa distribuzione dell’ambiente di esecuzione. Mescolare file di versioni diverse può causare errori di inizializzazione difficili da diagnosticare.

Un manifesto generato durante la compilazione, con l’elenco dei file previsti, le relative dimensioni e gli hash crittografici, può individuare risorse mancanti o non corrispondenti prima del rilascio.

Configurate la libreria di riconoscimento per utilizzare risorse ospitate sul vostro dominio e disabilitate il caricamento remoto dei modelli. In questo modo, se manca un modello locale viene restituito un errore esplicito, anziché avviare un download imprevisto da un host esterno.

Perché il microfono può mostrare il caricamento senza funzionare

Durante lo sviluppo, un sintomo comune è un pulsante del microfono che richiede il permesso, mostra un’animazione di caricamento e infine torna a uno stato di errore senza inserire alcun testo.

Il browser può perfino indicare che il microfono è in uso.

Questo non dimostra che il riconoscimento vocale funzioni. Dimostra soltanto che l’acquisizione dal microfono è stata autorizzata oppure è rimasta attiva in un certo momento.

Diversi errori indipendenti possono produrre sintomi quasi identici:

  1. Tipo MIME errato del modulo JavaScript. Il browser scarica un modulo dell’ambiente di esecuzione, ma si rifiuta di eseguirlo.
  2. Incompatibilità del formato di esecuzione del worker. Il worker compilato utilizza API dei worker classici, pur essendo avviato come worker di tipo modulo, o viceversa.
  3. Incompatibilità del decoder ONNX. L’ambiente di esecuzione crea la sessione dell’encoder, ma fallisce sul grafo del decoder quantizzato.
  4. Pipeline di riconoscimento incompleta. Le sessioni ONNX esistono, ma il tokenizer o il processore audio non è inizializzato correttamente.

Questi problemi devono essere diagnosticati separatamente. Risolverne uno può far emergere il successivo.

Il principio più importante per il debug è che una risposta HTTP 200 non equivale alla corretta inizializzazione del modello, e la corretta inizializzazione del modello non equivale a una trascrizione funzionante.

Persino una trascrizione riuscita non dimostra che il risultato arrivi davvero nel campo di testo dell’applicazione.

Ogni passaggio deve fornire un segnale diagnostico significativo.

Aggiungere osservabilità senza raccogliere il contenuto delle conversazioni private

Per avere log diagnostici utili non è necessario registrare quello che dice l’utente.

L’applicazione può monitorare stati tecnici come:

permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-inserted

In caso di errore, includete la fase, un codice tecnico di errore stabile e metadati dell’errore opportunamente ripuliti.

Un errore di inizializzazione del modello deve essere distinguibile da un permesso del microfono negato o da una registrazione vuota. Un errore del worker deve essere visibile durante lo sviluppo, invece di essere nascosto da un gestore di errori generico.

Non registrate campioni del microfono, frasi riconosciute, sequenze di token o altri contenuti vocali. La telemetria delle prestazioni può utilizzare fasce di durata, fascia del modello selezionato, tempi di inizializzazione e inferenza e categorie di errori.

Questo rende molto più semplice diagnosticare un microfono che non funziona, senza ridurre le garanzie di riservatezza.

Inserire la trascrizione tramite lo stato di React

Il riconoscimento vocale è utile solo quando la trascrizione finale arriva nel campo del modulo già esistente.

In un’applicazione React, quel campo è spesso controllato dallo stato del componente. Assegnare direttamente un valore a input.value può entrare in conflitto con il sistema di rendering del framework.

Il testo riconosciuto dovrebbe invece passare attraverso lo stesso meccanismo di aggiornamento dello stato utilizzato durante la normale digitazione.

L’integrazione deve anche tenere conto del comportamento asincrono. Il riconoscimento potrebbe terminare dopo che l’utente ha già digitato altri caratteri o spostato il cursore.

Usare un vecchio valore del campo, acquisito all’inizio della registrazione, può sovrascrivere per errore le modifiche più recenti.

Ecco uno schema semplificato di integrazione:

const insertion = insertTranscript({
  currentValue: latestValue,
  transcript: recognizedText,
  selection: currentSelection,
  maxLength: inputLimit,
});

setMessage(insertion.value);
restoreCaret(insertion.caret);

La funzione di supporto per l’inserimento dovrebbe conservare il testo attuale, eventualmente sostituire l’intervallo selezionato, aggiungere la spaziatura appropriata e rispettare la lunghezza massima del campo.

Unicode richiede attenzione perché la lunghezza delle stringhe JavaScript viene conteggiata in unità di codice UTF-16. Tagliare una stringa in una posizione arbitraria può dividere una coppia di surrogati.

Soprattutto, il riconoscimento vocale non deve mai inviare automaticamente il modulo. L’utente deve poter rileggere e correggere il testo prima dell’invio.

Gestire gli errori senza compromettere il normale inserimento di testo

Il sottosistema vocale deve essere facoltativo.

Se viene negato il permesso del microfono, il browser non riesce ad allocare il modello, un worker si interrompe o una registrazione non contiene parlato, il normale campo di input deve continuare a funzionare.

Rappresentate il ciclo di vita del microfono con stati espliciti, per esempio:

idle
preparing
listening
recognizing
error

Utilizzate etichette accessibili e indicazioni visibili dello stato. Un microfono che diventa semplicemente rosso o rosa dopo il caricamento comunica ben poco all’utente.

L’annullamento deve liberare le tracce audio, il contesto audio, i buffer in attesa e la sessione di registrazione attiva. L’interfaccia deve anche impedire che un risultato obsoleto di una registrazione annullata sovrascriva uno stato di input più recente.

Se un modello grande fallisce per limiti di memoria o problemi di inizializzazione, liberatelo completamente prima di provarne uno più piccolo. Limitate i tentativi, così il browser non continuerà a scaricare o inizializzare modelli che non è in grado di eseguire.

Non passate silenziosamente a un servizio vocale nel cloud quando il riconoscimento locale non funziona. Se l’elaborazione esclusivamente locale è un requisito vincolante, un errore del riconoscitore locale deve restare un errore locale.

Testare l’intera pipeline nel browser

Un controllo TypeScript superato o una compilazione di produzione riuscita non bastano a dimostrare che l’input vocale funzioni.

Allo stesso modo, verificare che il browser riesca a scaricare i file ONNX non dimostra che entrambe le sessioni del modello siano inizializzate correttamente.

Il test di regressione più utile deve esercitare lo stesso worker, gli stessi file dell’ambiente di esecuzione, il modello, il processore e il tokenizer usati in produzione.

Fornite alla vera pipeline di inferenza del browser una registrazione PCM breve e nota, e verificate che venga restituita una trascrizione non vuota.

Poi testate l’intera interazione con il microfono in un browser reale.

  • Caricamento iniziale della pagina: verificare che nessun modello vocale o file dell’ambiente di esecuzione venga scaricato prima dell’uso del microfono.
  • Inizializzazione a freddo: verificare il permesso del microfono, le richieste dei modelli, i tipi MIME JavaScript e WASM e una reale conferma che il worker è pronto.
  • Inizializzazione ONNX: verificare che sia possibile creare le sessioni di encoder e decoder.
  • Acquisizione audio: confermare che AudioWorklet produca buffer PCM non vuoti.
  • Ricampionamento: confermare che il worker riceva la frequenza di campionamento reale e produca audio valido a 16 kHz.
  • Arresto manuale: verificare che un secondo clic sul microfono interrompa la registrazione in modo affidabile.
  • Arresto automatico: verificare che le pause naturali terminino la registrazione senza tagliare le parole pronunciate a bassa voce.
  • Trascrizione: verificare che processore, modello e tokenizer insieme restituiscano un testo riconoscibile.
  • Integrazione con il campo di input: verificare che il testo riconosciuto compaia nel vero campo controllato senza sovrascrivere le modifiche dell’utente.
  • Recupero dagli errori: provare permessi negati, file mancanti, errori del worker, inizializzazione del modello non riuscita, annullamento e ripiego su un modello più piccolo.
  • Riservatezza: controllare le richieste di rete e confermare che l’audio del microfono non venga caricato su un server o su un servizio di analisi.
  • Compatibilità mobile: ripetere i test su telefoni reali, inclusi avvii a freddo, avvii a caldo e sessioni di dettatura ripetute.

I test su registrazioni PCM sono ottimi per convalidare la pipeline di inferenza, ma non dimostrano che l’acquisizione dal microfono funzioni. Il test di accettazione finale deve includere il microfono reale e l’effettiva interfaccia utente.

Un’architettura pratica per una dettatura affidabile nel browser

Per un sito che richiede input vocale breve, multilingue e rispettoso della riservatezza, l’architettura più pratica è volutamente semplice:

Pulsante microfono React
        |
        v
Richiesta del permesso
        |
        v
Inizializzazione differita del modello
        |
        v
Acquisizione PCM con AudioWorklet
        |
        v
RMS adattivo / arresto manuale
        |
        v
Worker dedicato
        |
        v
Ricampionamento audio a 16 kHz
        |
        v
Whisper Tiny o Base Q8
        |
        v
ONNX Runtime WASM compatibile
        |
        v
Processore Whisper + tokenizer
        |
        v
Trascrizione finale
        |
        v
Campo React controllato

L’applicazione dovrebbe ospitare direttamente i file versionati del modello e dell’ambiente di esecuzione, mantenere l’inferenza fuori dal thread principale, evitare download inutili dei modelli, liberare le risorse dopo l’uso e non caricare mai l’audio del microfono su un server per la trascrizione.

Opzioni più sofisticate, come l’accelerazione WebGPU, il rilevamento neurale dell’attività vocale, la trascrizione parziale continua o ulteriori fasce di modelli, possono essere valutate in seguito quando le misurazioni ne giustificheranno la complessità.

La lezione più importante non riguarda una particolare versione di libreria o dimensione del modello. Riguarda la necessità di verificare ogni punto di collegamento della pipeline di riconoscimento vocale nel browser.

Un prompt funzionante per concedere il permesso al microfono non significa che l’audio venga trascritto. Un modello scaricato non significa che il suo decoder sia stato inizializzato. Una sessione ONNX creata correttamente non significa che il tokenizer sia disponibile. E una trascrizione riuscita non significa che l’applicazione abbia inserito correttamente il testo.

La funzionalità è completa soltanto quando chi visita il sito può premere il microfono, parlare normalmente, fermare la registrazione e vedere le parole riconosciute comparire nel campo modificabile, senza inviare la registrazione a un server di riconoscimento vocale.