Lingua

Contattami

Estensione per Chrome · dal 2024

Voice Master

Estensione per Chrome che scrive il parlato dettato in qualsiasi campo di testo di qualsiasi pagina web, con correzione AI facoltativa tramite la chiave API dell'utente.

Rilasciato
Utenti
chi sono 2000
Lingue dell'interfaccia
17
Prezzo
Gratuito
  • JavaScript (ES modules)
  • Chrome Extensions Manifest V3
  • Web Speech API (webkitSpeechRecognition)
  • Web Crypto API (AES-GCM)
  • chrome.storage / chrome.scripting / chrome.i18n
  • OpenAI, Anthropic, DeepSeek and Gemini HTTP APIs
Chrome Web Store chromewebstore.google.com

Chi sono

Voice Master inserisce nella pagina corrente un pannello trascinabile, esegue la Web Speech API di Chrome e inserisce il testo riconosciuto alla posizione del cursore dell'ultimo campo messo a fuoco: input, textarea, contenteditable o TinyMCE. Sopra a tutto questo ci sono un pannello di dettatura con una propria area di testo, tasti rapidi, 33 lingue di dettatura e un'interfaccia in 17 lingue.

La parte difficile è il motore di riconoscimento stesso. webkitSpeechRecognition in modalità continua termina le sessioni da solo dopo le pause e dopo alcuni minuti di lavoro, e lo segnala solo tramite onend. L'estensione mantiene viva una sessione di dettatura con un watchdog separato che rianima un motore fermo e riavvia uno in esecuzione a intervalli di tempo, consegnando il testo in sospeso al campo prima di ogni riavvio pianificato.

La correzione AI facoltativa invia i frammenti dettati a OpenAI, Anthropic, DeepSeek o Gemini con la chiave dell'utente. Le chiamate partono dal service worker, i risultati vengono inseriti nell'ordine in cui sono stati pronunciati, e una chiamata fallita ripiega sul testo grezzo. Le chiavi memorizzate sono cifrate a riposo e non vengono inviate al JavaScript della pagina.

Voice Master · riconoscimento vocale

cosa è stato risolto

ATTIVOdal 2024

Mantenere vivo il riconoscimento

nel prodotto

Cosa offreUna dettatura lunga non si ferma da sola dopo una pausa o dopo alcuni minuti, e nulla di ciò che è stato detto subito prima di un riavvio va perso.

Problema
Il riconoscimento vocale continuo di Chrome termina le sessioni da solo dopo le pause e dopo alcuni minuti, senza alcun segnale affidabile se non onend. Una dettatura lunga si ferma in silenzio.
Soluzione

Una classe Heartbeat separata interroga lo stato del motore ogni 100 ms, rianima un motore fermo dopo un periodo di tolleranza e riavvia uno in esecuzione a intervalli di tempo, e l'host consegna il testo in sospeso prima di ogni riavvio pianificato.

Stack
  • Web Speech API
  • JavaScript
Attività · 4
Il motore si ferma da solo dopo una pausa e la dettatura muoreIl watchdog scatta ogni 100 ms e riavvia il motore dopo 1.5 s di inattività confermata

Heartbeat.tick() chiede all'host se il riconoscimento è in esecuzione. Quando non lo è, registra il momento del decesso e attende un periodo di tolleranza di 1.5 s prima di chiamare onRevive(), così un normale ciclo di stop/start all'interno di Chrome non viene disturbato.

Un solo riavvio alla volta può essere in corso. Se onstart non arriva entro 3 s, il riavvio in sospeso viene scartato e prima del tentativo successivo inizia un nuovo periodo di tolleranza di 1.5 s. Il flag recognizing viene impostato solo da onstart tramite noteEngineUp(), mai in modo ottimistico, così uno start() fallito (InvalidStateError) non può far credere al watchdog che il motore sia attivo.

Chrome termina una sessione lunga da solo, nel momento che sceglie luiAggiornamento ogni 270 s: consegnare il testo in sospeso, fermare il motore, lasciare che il watchdog lo rianimi

Il watchdog conserva l'ora di inizio della sotto-sessione attiva corrente. Dopo sessionMaxMs (270000 ms, impostato in speech.js) chiama refreshSession(), che prima consegna al campo di destinazione la trascrizione finale accumulata e l'ultima frase provvisoria, poi ferma il motore. end() vede che lo stop non era manuale e ritorna senza finalizzare; i tick successivi rianimano una sessione nuova.

L'intervallo è scelto per cadere prima del limite di circa cinque minuti osservato dall'autore in Chrome (indicato nei commenti del codice), così il riavvio avviene in un momento controllato dall'estensione invece che in uno scelto da Chrome.

Il riavvio automatico non deve contrastare l'utente né un microfono negatoFlag separati manualStop e fatalError condizionano ogni riavvio; solo gli errori di microfono e permessi lo fermano definitivamente

onend scatta per ogni tipo di stop, quindi end() non può distinguerli da solo. L'host mantiene due flag: manualStop è impostato solo dallo stop dell'utente (pulsante, tasto rapido, chiusura del pannello), fatalError solo dagli errori audio-capture e not-allowed. Il watchdog e reviveRecognition() controllano entrambi e restano inattivi quando uno dei due è impostato; ogni altro errore, come no-speech, lascia la sessione da rianimare.

Per not-allowed il gestore confronta l'ora dell'errore con l'ora del clic: un errore entro 100 ms significa che il permesso è bloccato da una policy, uno successivo significa che l'utente ha negato la richiesta, e il pannello mostra un messaggio diverso per ciascun caso.

Logica di riavvio intrecciata con la gestione grezza della Web Speech APIUn sottile wrapper RecognitionEngine emette callback; tutto lo stato di riavvio resta in un unico host

RecognitionEngine possiede solo l'oggetto webkitSpeechRecognition (continuo, risultati provvisori, 5 alternative) e analizza i risultati in finalText, interimText e hadFinal. start() lascia deliberatamente propagare InvalidStateError perché il chiamante decida se riprovare.

Flag, accumulo e decisioni di riavvio stanno in speech.js, e la politica dei tempi sta in Heartbeat. Le tre parti comunicano tramite piccole interfacce di callback (isRecognizing, onRevive, onRefresh, onSilence), così il watchdog può essere regolato senza toccare il codice di riconoscimento.

Portare il testo in qualsiasi campo

nel prodotto

Cosa offreLe parole dette finiscono dove si trovava il cursore, in qualsiasi casella di testo o editor di un sito, e ogni frase viene digitata solo quando è terminata.

Problema
Il testo riconosciuto deve finire alla posizione del cursore del campo in cui l'utente stava scrivendo, su pagine che l'estensione non controlla, mentre l'utente fa clic sul pannello dell'estensione stessa.
Soluzione

Un TextInserter tiene traccia dell'ultimo elemento idoneo messo a fuoco e usa il metodo di inserimento che ogni tipo di editor accetta; il pannello evita di rubare il focus.

Stack
  • DOM
  • Selection API
  • document.execCommand
  • TinyMCE API
Attività · 3
I campi differiscono: input, textarea, contenteditable, TinyMCEUn solo insert() sceglie splice, execCommand('insertText') o mceInsertContent in base al tipo di destinazione

Per input e textarea il valore viene spezzato tra selectionStart e selectionEnd e il cursore viene spostato oltre il nuovo testo, così un frammento selezionato è sostituito da quello dettato. Per contenteditable usa execCommand('insertText'), che sostituisce anch'esso la selezione corrente e fa vedere all'editor una normale modifica. Per TinyMCE chiama il comando mceInsertContent dell'editor stesso.

TinyMCE si inizializza tardi, quindi il suo hook di focus viene collegato dopo un ritardo e, se nient'altro era ancora a fuoco, l'editor attivo viene messo a fuoco e tracciato.

Fare clic sul pannello sposta il focus lontano dal campo di destinazioneTracciare il focus in fase di capture, conservare l'ultimo campo idoneo; impedire il furto del focus sui controlli

Un listener di focus in fase di capture su document registra ogni elemento idoneo in un elenco con il più recente per ultimo, così quando l'utente fa clic sul pannello la destinazione dell'inserimento è ancora l'ultimo campo in cui si trovava.

Il pulsante di avvio annulla mousedown e i selettori di lingua annullano mouseup, così premerli non toglie il focus al campo. Il pannello trascinabile salta la gestione del trascinamento su input, pulsanti e link perché mantengano il normale comportamento di focus e clic.

I risultati provvisori cambiano di continuo e non devono essere digitati in anticipoMostrare il testo provvisorio in un'anteprima; consegnare la frase solo quando il flusso provvisorio si svuota

I risultati finali si accumulano in finalTranscript. Il testo provvisorio va a un pannello di anteprima trascinabile separato e viene ricordato come prev_text; quando arriva un aggiornamento con la parte provvisoria vuota, buffer() consegna la frase ricordata al campo e svuota l'anteprima.

Lo span finale viene ridisegnato solo quando è arrivato un nuovo risultato finale, il che mantiene basso il lavoro sul DOM durante una dettatura lunga.

Pipeline di correzione AI

nel prodotto

Cosa offreUn'AI a scelta dell'utente può correggere le parole fraintese in base al contesto. Il testo mantiene l'ordine del parlato, e se l'AI fallisce il testo grezzo arriva comunque.

Problema
Quattro provider AI con API diverse, chiamati dall'interno di pagine web arbitrarie con la loro CSP, mentre la dettatura continua a produrre nuovi frammenti che devono restare in ordine.
Soluzione

Gli adattatori dei provider girano nel service worker; la pagina comunica con essi tramite un bridge di eventi correlati, e una coda FIFO con timeout e ripiego sul testo grezzo alimenta il campo.

Stack
  • Service worker
  • fetch
  • CustomEvent
  • Promises
Attività · 5
CSP e CORS della pagina bloccano le chiamate alle API AI dalla paginaTutte le chiamate API girano nel service worker MV3 con host_permissions per i quattro host

Il correttore lato pagina costruisce soltanto il prompt. La richiesta passa attraverso il content script fino al service worker, che possiede il fetch. host_permissions in manifest.json copre api.openai.com, api.anthropic.com, api.deepseek.com e generativelanguage.googleapis.com.

Il listener onMessage del worker restituisce true per tenere aperto il canale per la risposta asincrona.

I quattro provider differiscono per autenticazione e forma di richieste e risposteTabella di adattatori: per ogni provider un URL, un costruttore di intestazioni, un costruttore del corpo, un parser e un lettore degli errori

AI_PROVIDERS descrive ogni API in modo dichiarativo: intestazione Bearer per OpenAI e DeepSeek, x-api-key più intestazioni di versione per Anthropic, x-goog-api-key per Gemini, e il percorso del testo in ogni risposta. aiCorrect() è identica per tutti.

Una chiave vuota restituisce il codice 'no_key' invece di un errore, il che permette ai chiamanti di ripiegare in silenzio sul testo grezzo. Gemini è stato aggiunto come quarto provider con una voce di tabella più un permesso host nel manifest.

Le risposte AI asincrone potrebbero inserire i frammenti fuori dall'ordine del parlatoCoda FIFO a catena di promise, timeout di 20 s per frammento, testo grezzo inserito in caso di qualsiasi errore

LiveImprover.push() accoda ogni frammento a un'unica catena di promise, così il frammento N+1 viene inviato solo dopo che il frammento N è stato inserito. Ogni chiamata corre contro un timeout di 20 s. In caso di errore o timeout viene inserito il testo originale, così il parlato dettato non va mai perso.

I modelli restituiscono testo senza spazi ai margini, quindi keepSpacing() ripristina gli spazi iniziali e finali originali del frammento per evitare che frammenti consecutivi si attacchino. Senza chiave la coda segnala il codice 'no_key' e l'interfaccia resta in silenzio, come se la modalità live fosse spenta.

Il mondo della pagina e quello dell'estensione possono scambiarsi solo eventiID di richiesta e una mappa di promise in sospeso trasformano gli eventi della finestra in chiamate asincrone

Il codice del pannello gira come module script nella pagina e non ha API chrome.*. GptCorrector.improve() assegna un id di sequenza, memorizza il resolver in una Map e invia stt_ai_correct_request. Il content script lo inoltra al worker e risponde con stt_ai_correct_response che porta lo stesso id, il quale risolve esattamente una promise in sospeso.

Il motore vocale fraintende le parole; un semplice controllo ortografico le lasciaUn prompt localizzato dice al modello che il testo è STT grezzo e di correggere le parole in base al contesto

Il prompt corr_and_return afferma che il testo proviene da un riconoscitore vocale imperfetto, chiede al modello di sostituire le parole riconosciute male con ciò che chi parla intendeva più probabilmente, di mantenere lingua e significato, di non aggiungere nulla e di restituire solo il testo. È tradotto in ogni locale dell'interfaccia.

Nel pannello di dettatura il testo migliorato sostituisce il contenuto della textarea tramite execCommand('insertText'), così Ctrl+Z ripristina la versione originale.

Gestione delle chiavi API

shipped · 2026

Cosa offreLe chiavi AI dell'utente sono conservate cifrate, e i siti web in cui compare il pannello non possono leggerle. Le impostazioni restano le stesse in ogni scheda aperta.

Problema
Le chiavi API degli utenti devono essere conservate da un'estensione la cui interfaccia gira dentro pagine non attendibili, quindi qualsiasi script della pagina potrebbe leggere ciò che il pannello può leggere.
Soluzione

Le chiavi sono cifrate e decifrate nel service worker, tenute in chiaro solo nella memoria del content script, e la pagina riceve soltanto flag e indizi mascherati.

Stack
  • Web Crypto API (AES-GCM)
  • chrome.storage.local
  • Service worker
Attività · 4
Le chiavi API non devono stare in chiaro nello storage dell'estensioneAES-GCM con un segreto casuale a 256 bit per installazione e un IV nuovo di 12 byte per ogni valore

sttCryptoKey() genera il segreto una volta, lo memorizza e lo importa come CryptoKey non estraibile. I valori cifrati portano un prefisso di versione (stt1:) seguito da IV e testo cifrato in base64.

La crittografia gira nel service worker perché esso è sempre un contesto sicuro con crypto.subtle, mentre un content script su una pagina http semplice non lo è. Il commento nel codice è esplicito nel dire che si tratta di una protezione a riposo contro letture casuali, non contro chi ha l'intero profilo del browser.

Gli script della pagina non devono poter leggere le chiavi memorizzateGli eventi della pagina portano solo flag hasKeys e indizi mascherati; il testo in chiaro resta nel content script

sttPublicCfg() è l'unica forma che passa nella pagina: provider, flag live, un booleano per provider e un indizio come i primi 3 e gli ultimi 4 caratteri. Il modulo delle impostazioni non riceve mai la chiave memorizzata; un campo non toccato al Salvataggio significa mantenere la chiave memorizzata (flag keyTouched).

Gli utenti esistenti avevano chiavi in chiaro in un vecchio formato di archiviazioneAlla prima lettura, le chiavi legacy e in chiaro vengono risalvate cifrate senza intervento dell'utente

sttGetAi() sposta la vecchia gptApiKey singola nella struttura per provider, cifra ogni valore senza il prefisso stt1:, riscrive il risultato e rimuove la cella legacy. Se il worker non riesce a cifrare, il testo in chiaro viene mantenuto così l'utente non resta bloccato fuori, e la migrazione viene ritentata alla lettura successiva.

sttDecrypt() restituisce una stringa vuota su un valore corrotto o estraneo, così un record rotto si comporta come 'nessuna chiave' invece di generare un'eccezione.

Le impostazioni devono restare aggiornate in ogni scheda apertastorage.onChanged ritrasmette la configurazione sanitizzata allo specchio nella pagina di ogni scheda

Il pannello di ogni scheda conserva uno specchio locale di provider, flag live e hasKeys. content.js ascolta chrome.storage.onChanged per il record sttAi e invia alla pagina una nuova configurazione sanitizzata, così liveEnabled() non agisce mai su una chiave che è stata eliminata altrove.

Eliminare una chiave nel popover è un'azione locale fino a Salva; chiudere senza salvare mantiene la chiave memorizzata.

Funzionare dentro qualsiasi pagina

nel prodotto

Cosa offreIl tasto rapido funziona anche nelle schede aperte prima che l'estensione venisse caricata, e il pannello parla la lingua dell'utente su qualsiasi sito.

Problema
Il pannello vive dentro pagine di terze parti: i content script possono mancare e il codice nel mondo della pagina non ha API chrome.*.
Soluzione

Iniezione su richiesta con un percorso di ripiego e un bridge di localizzazione nello storage della pagina.

Stack
  • chrome.scripting
  • chrome.i18n
Attività · 2
Tasto rapido premuto in una scheda in cui il content script non è caricatoProvare sendMessage; su lastError iniettare content.js con chrome.scripting, poi rinviare

start() nel service worker invia l'attivazione alla scheda. Se chrome.runtime.lastError mostra che nessuno è in ascolto, inietta content.js e ripete il messaggio. Il modulo del pannello e il foglio di stile vengono poi iniettati su richiesta, saltando le pagine chrome://.

Il popup controlla allo stesso modo con un ping isLoaded prima di decidere se inviare il messaggio alla scheda o passare dal worker.

Il codice del pannello nel mondo della pagina non può chiamare chrome.i18nIl content script copia ogni messaggio nel localStorage della pagina; en fornisce l'elenco delle chiavi

getLocalizationKeys() legge l'elenco delle chiavi dal messages.json della locale predefinita, che ha l'insieme completo, poi memorizza chrome.i18n.getMessage(key) per ciascuna con prefisso stt_. Helper.getI18nMessage() nella pagina legge da lì.

Poiché l'elenco delle chiavi proviene da en e i valori da chrome.i18n, una locale con chiavi mancanti ripiega sull'inglese chiave per chiave. La locale hi al momento manca di 7 chiavi ed è coperta in questo modo.

Numeri

dal prodotto
  • Utenti circa 2,000 Chrome Web Store.
  • Lingue dell'interfaccia 17
  • Lingue di dettatura 33 63 varianti regionali.