Sprache

Kontakt aufnehmen

Chrome-Erweiterung · seit 2024

Voice Master

Chrome-Erweiterung, die diktierte Sprache in jedes Textfeld auf jeder Webseite tippt, mit optionaler KI-Bereinigung über den eigenen API-Schlüssel des Benutzers.

Veröffentlicht
Nutzer
etwa 2.000
Oberflächensprachen
17
Preis
Kostenlos
  • 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

Über mich

Voice Master blendet ein verschiebbares Panel in die aktuelle Seite ein, startet die Web Speech API von Chrome und fügt erkannten Text an der Einfügemarke des zuletzt fokussierten Feldes ein: input, textarea, contenteditable oder TinyMCE. Darauf setzen ein Diktier-Panel mit eigenem Textbereich, Hotkeys, 33 Diktiersprachen und eine Oberfläche in 17 Sprachen auf.

Schwierig ist die Erkennungs-Engine selbst. webkitSpeechRecognition im kontinuierlichen Modus beendet Sitzungen von sich aus nach Pausen und nach einigen Minuten Betrieb und meldet das nur über onend. Die Erweiterung hält eine Diktiersitzung mit einem separaten Watchdog am Leben, der eine gestoppte Engine wiederbelebt und eine laufende per Timer neu startet, wobei vor jedem geplanten Neustart noch ausstehender Text an das Feld übergeben wird.

Die optionale KI-Korrektur sendet diktierte Textabschnitte mit dem eigenen Schlüssel des Benutzers an OpenAI, Anthropic, DeepSeek oder Gemini. Die Aufrufe gehen vom Service Worker aus, die Ergebnisse werden in gesprochener Reihenfolge eingefügt, und ein fehlgeschlagener Aufruf fällt auf den Rohtext zurück. Gespeicherte Schlüssel sind im Ruhezustand verschlüsselt und werden nicht an das JavaScript der Seite weitergegeben.

Voice Master · Spracherkennung

was gelöst wurde

LIVEseit 2024

Die Erkennung am Leben halten

im Produkt

Was es bringtEin langes Diktat bricht nach einer Pause oder nach einigen Minuten nicht von selbst ab, und nichts, was kurz vor einem Neustart gesagt wurde, geht verloren.

Problem
Die kontinuierliche Spracherkennung von Chrome beendet Sitzungen von selbst nach Pausen und nach einigen Minuten, ohne verlässliches Signal außer onend. Ein langes Diktat bricht still ab.
Lösung

Eine separate Klasse Heartbeat fragt den Zustand der Engine alle 100 ms ab, belebt eine gestoppte Engine nach einer Karenzzeit wieder und startet eine laufende per Timer neu, und der Host übergibt vor jedem geplanten Neustart ausstehenden Text.

Stack
  • Web Speech API
  • JavaScript
Aufgaben · 4
Die Engine stoppt nach einer Pause von selbst, und das Diktat ist totDer Watchdog tickt alle 100 ms und startet die Engine nach 1.5 s bestätigter Ausfallzeit neu

Heartbeat.tick() fragt den Host, ob die Erkennung läuft. Läuft sie nicht, hält er den Zeitpunkt des Ausfalls fest und wartet eine Karenzzeit von 1.5 s, bevor er onRevive() aufruft, damit ein normaler Stopp/Start-Zyklus innerhalb von Chrome nicht gestört wird.

Es ist immer nur eine Wiederbelebung gleichzeitig unterwegs. Trifft onstart nicht innerhalb von 3 s ein, wird die ausstehende Wiederbelebung verworfen, und vor dem nächsten Versuch beginnt eine neue Karenzzeit von 1.5 s. Das Flag recognizing wird nur von onstart über noteEngineUp() gesetzt, nie optimistisch, sodass ein fehlgeschlagenes start() (InvalidStateError) den Watchdog nicht glauben lassen kann, die Engine laufe.

Chrome beendet eine lange Sitzung von selbst, zu einem Zeitpunkt seiner WahlAlle 270 s auffrischen: ausstehenden Text übergeben, Engine stoppen, vom Watchdog wiederbeleben lassen

Der Watchdog merkt sich die Startzeit der aktuellen laufenden Teilsitzung. Nach sessionMaxMs (270000 ms, in speech.js gesetzt) ruft er refreshSession() auf, das zuerst das angesammelte finale Transkript und die letzte vorläufige Phrase an das Zielfeld übergibt und dann die Engine stoppt. end() erkennt, dass der Stopp nicht manuell war, und kehrt ohne Finalisierung zurück; die nächsten Ticks beleben eine frische Sitzung.

Das Intervall ist so gewählt, dass es vor dem Limit von etwa fünf Minuten liegt, das der Autor in Chrome beobachtet hat (in den Code-Kommentaren festgehalten), sodass der Neustart zu einem Zeitpunkt erfolgt, den die Erweiterung kontrolliert, statt zu einem, den Chrome wählt.

Der automatische Neustart darf nicht gegen den Benutzer oder ein verweigertes Mikrofon arbeitenGetrennte Flags manualStop und fatalError steuern jede Wiederbelebung; nur Mikrofon- und Berechtigungsfehler stoppen sie dauerhaft

onend feuert bei jeder Art von Stopp, daher kann end() sie nicht von selbst unterscheiden. Der Host führt zwei Flags: manualStop wird nur durch den Stopp des Benutzers gesetzt (Button, Hotkey, Schließen des Panels), fatalError nur durch audio-capture- und not-allowed-Fehler. Der Watchdog und reviveRecognition() prüfen beide und bleiben untätig, sobald eines gesetzt ist; jeder andere Fehler, etwa no-speech, lässt die Sitzung wiederbeleben.

Bei not-allowed vergleicht der Handler den Zeitpunkt des Fehlers mit dem Zeitpunkt des Klicks: Ein Fehler innerhalb von 100 ms bedeutet, dass die Berechtigung per Richtlinie gesperrt ist, ein späterer, dass der Benutzer die Abfrage abgelehnt hat, und das Panel zeigt für beide Fälle eine andere Meldung.

Neustartlogik mit der Rohverarbeitung der Web Speech API verwobenEin dünner Wrapper RecognitionEngine löst Callbacks aus; der gesamte Neustartzustand bleibt in einem Host

RecognitionEngine besitzt nur das Objekt webkitSpeechRecognition (continuous, interim results, 5 Alternativen) und zerlegt Ergebnisse in finalText, interimText und hadFinal. start() lässt InvalidStateError bewusst durchlaufen, damit der Aufrufer entscheidet, ob er es erneut versucht.

Flags, Ansammlung und Neustartentscheidungen liegen in speech.js, und die Zeitsteuerung liegt in Heartbeat. Die drei Teile sprechen über kleine Callback-Schnittstellen (isRecognizing, onRevive, onRefresh, onSilence), sodass sich der Watchdog abstimmen lässt, ohne den Erkennungscode anzufassen.

Text in jedes Feld bringen

im Produkt

Was es bringtGesprochene Wörter landen dort, wo der Cursor war, in jedem Textfeld und Editor einer Website, und jede Phrase wird erst getippt, wenn sie abgeschlossen ist.

Problem
Erkannter Text muss an der Einfügemarke des Feldes landen, in das der Benutzer gerade getippt hat, auf Seiten, die die Erweiterung nicht kontrolliert, während der Benutzer auf das eigene Panel der Erweiterung klickt.
Lösung

Ein TextInserter merkt sich das zuletzt fokussierte geeignete Element und nutzt die Einfügemethode, die der jeweilige Editortyp akzeptiert; das Panel vermeidet es, den Fokus zu stehlen.

Stack
  • DOM
  • Selection API
  • document.execCommand
  • TinyMCE API
Aufgaben · 3
Felder unterscheiden sich: input, textarea, contenteditable, TinyMCEEin insert() wählt je Zieltyp Splice, execCommand('insertText') oder mceInsertContent

Bei input und textarea wird der Wert zwischen selectionStart und selectionEnd gespleißt und die Einfügemarke hinter den neuen Text bewegt, sodass ein markiertes Fragment durch das diktierte ersetzt wird. Bei contenteditable nutzt es execCommand('insertText'), das ebenfalls die aktuelle Auswahl ersetzt und den Editor eine normale Bearbeitung sehen lässt. Bei TinyMCE ruft es den eigenen Befehl mceInsertContent des Editors auf.

TinyMCE initialisiert sich spät, daher wird der Fokus-Hook dafür verzögert angehängt und, falls noch nichts anderes fokussiert war, der aktive Editor fokussiert und verfolgt.

Ein Klick auf das Panel nimmt den Fokus vom ZielfeldFokus in der Capture-Phase verfolgen, das zuletzt geeignete Feld behalten; Fokusraub auf Steuerelementen blockieren

Ein Fokus-Listener in der Capture-Phase auf document erfasst jedes geeignete Element in einer Liste mit dem jüngsten zuletzt, sodass beim Klick auf das Panel das Einfügeziel weiterhin das letzte Feld ist, in dem der Benutzer war.

Der Start-Button bricht mousedown ab, und die Sprachauswahlen brechen mouseup ab, sodass ihr Drücken das Feld nicht unscharf macht. Das verschiebbare Panel überspringt die Drag-Behandlung auf Eingabefeldern, Buttons und Links, damit diese ihr normales Fokus- und Klickverhalten behalten.

Zwischenergebnisse ändern sich ständig und dürfen nicht zu früh getippt werdenZwischentext in einer Vorschau zeigen; die Phrase erst übergeben, wenn der Zwischenstrom leer wird

Finale Ergebnisse sammeln sich in finalTranscript. Zwischentext geht in ein separates, verschiebbares Vorschau-Panel und wird als prev_text gemerkt; kommt ein Update mit leerem Zwischenteil, übergibt buffer() die gemerkte Phrase an das Feld und leert die Vorschau.

Der finale Abschnitt wird nur neu gerendert, wenn ein neues finales Ergebnis eingetroffen ist, was die DOM-Arbeit bei langem Diktat gering hält.

Pipeline der KI-Korrektur

im Produkt

Was es bringtEine KI nach Wahl des Benutzers kann falsch verstandene Wörter anhand des Kontexts korrigieren. Der Text behält seine gesprochene Reihenfolge, und fällt die KI aus, kommt trotzdem der Rohtext an.

Problem
Vier KI-Anbieter mit unterschiedlichen APIs, aufgerufen aus beliebigen Webseiten mit eigener CSP, während das Diktat laufend neue Abschnitte erzeugt, die in der richtigen Reihenfolge bleiben müssen.
Lösung

Anbieter-Adapter laufen im Service Worker; die Seite spricht mit ihnen über eine korrelierte Event-Brücke, und eine FIFO-Warteschlange mit Timeout und Rohtext-Fallback speist das Feld.

Stack
  • Service worker
  • fetch
  • CustomEvent
  • Promises
Aufgaben · 5
CSP und CORS der Seite blockieren Aufrufe von KI-APIs aus der SeiteAlle API-Aufrufe laufen im MV3-Service-Worker mit host_permissions für die vier Hosts

Der Corrector auf der Seite baut nur den Prompt. Der Request läuft über das Content Script zum Service Worker, der den fetch besitzt. host_permissions in manifest.json decken api.openai.com, api.anthropic.com, api.deepseek.com und generativelanguage.googleapis.com ab.

Der onMessage-Listener des Workers gibt true zurück, um den Kanal für die asynchrone Antwort offen zu halten.

Vier Anbieter unterscheiden sich in Authentifizierung sowie Request- und Response-FormAdapter-Tabelle: pro Anbieter eine URL, ein Header-Builder, ein Body-Builder, ein Parser und ein Fehlerleser

AI_PROVIDERS beschreibt jede API deklarativ: Bearer-Header für OpenAI und DeepSeek, x-api-key plus Versions-Header für Anthropic, x-goog-api-key für Gemini und den Pfad zum Text in jeder Antwort. aiCorrect() ist für alle gleich.

Ein leerer Schlüssel liefert den Code 'no_key' statt eines Fehlers, wodurch Aufrufer still auf den Rohtext zurückfallen können. Gemini wurde als vierter Anbieter durch einen Tabelleneintrag plus eine Host-Berechtigung im Manifest ergänzt.

Asynchrone KI-Antworten könnten Abschnitte außerhalb der gesprochenen Reihenfolge einfügenFIFO-Warteschlange als Promise-Kette, 20 s Timeout pro Abschnitt, bei jedem Fehler wird Rohtext eingefügt

LiveImprover.push() hängt jeden Abschnitt an eine einzige Promise-Kette an, sodass Abschnitt N+1 erst gesendet wird, nachdem Abschnitt N eingefügt wurde. Jeder Aufruf läuft im Wettlauf mit einem Timeout von 20 s. Bei Fehler oder Timeout wird der Originaltext eingefügt, sodass diktierte Sprache nie verloren geht.

Modelle liefern getrimmten Text, daher stellt keepSpacing() die ursprünglichen führenden und nachfolgenden Leerzeichen des Abschnitts wieder her, damit aufeinanderfolgende Abschnitte nicht zusammenkleben. Ohne Schlüssel meldet die Warteschlange den Code 'no_key', und die Oberfläche bleibt still, als wäre der Live-Modus aus.

Seitenwelt und Erweiterungswelt können nur Events austauschenRequest-IDs und eine Map ausstehender Promises machen aus Window-Events asynchrone Aufrufe

Der Panel-Code läuft als Modul-Skript in der Seite und hat keine chrome.*-APIs. GptCorrector.improve() vergibt eine laufende ID, legt den Resolver in einer Map ab und sendet stt_ai_correct_request. Das Content Script leitet ihn an den Worker weiter und antwortet mit stt_ai_correct_response, das dieselbe ID trägt und genau ein ausstehendes Promise auflöst.

Die Spracherkennung versteht Wörter falsch; eine einfache Rechtschreibprüfung lässt sie stehenEin lokalisierter Prompt sagt dem Modell, dass der Text rohes STT ist und Wörter anhand des Kontexts zu korrigieren sind

Der Prompt corr_and_return besagt, dass der Text von einem unvollkommenen Spracherkenner stammt, bittet das Modell, falsch erkannte Wörter durch das zu ersetzen, was der Sprecher höchstwahrscheinlich meinte, Sprache und Bedeutung beizubehalten, nichts hinzuzufügen und nur den Text zurückzugeben. Er ist in jede UI-Locale übersetzt.

Im Diktier-Panel ersetzt der verbesserte Text den Inhalt der Textarea über execCommand('insertText'), sodass Strg+Z die Originalversion wiederherstellt.

Umgang mit API-Schlüsseln

shipped · 2026

Was es bringtDie eigenen KI-Schlüssel des Benutzers werden verschlüsselt gespeichert, und die Websites, auf denen das Panel erscheint, können sie nicht lesen. Die Einstellungen bleiben in jedem offenen Tab gleich.

Problem
API-Schlüssel der Benutzer müssen von einer Erweiterung gespeichert werden, deren Oberfläche in nicht vertrauenswürdigen Seiten läuft, sodass jedes Seitenskript lesen könnte, was das Panel lesen kann.
Lösung

Schlüssel werden im Service Worker ver- und entschlüsselt, nur im Speicher des Content Scripts im Klartext gehalten, und die Seite erhält nur Flags und maskierte Hinweise.

Stack
  • Web Crypto API (AES-GCM)
  • chrome.storage.local
  • Service worker
Aufgaben · 4
API-Schlüssel dürfen nicht im Klartext im Speicher der Erweiterung liegenAES-GCM mit einem zufälligen 256-Bit-Geheimnis pro Installation und einem frischen 12-Byte-IV pro Wert

sttCryptoKey() erzeugt das Geheimnis einmal, speichert es und importiert es als nicht exportierbaren CryptoKey. Verschlüsselte Werte tragen ein Versionspräfix (stt1:), gefolgt von IV und Chiffretext in Base64.

Die Kryptografie läuft im Service Worker, weil dieser stets ein sicherer Kontext mit crypto.subtle ist, ein Content Script auf einer einfachen http-Seite dagegen nicht. Der Code-Kommentar sagt ausdrücklich, dass dies Schutz im Ruhezustand gegen beiläufiges Lesen ist, nicht gegen jemanden, der das gesamte Browserprofil besitzt.

Seitenskripte dürfen gespeicherte Schlüssel nicht lesen könnenSeiten-Events tragen nur hasKeys-Flags und maskierte Hinweise; Klartext bleibt im Content Script

sttPublicCfg() ist die einzige Form, die in die Seite gelangt: Anbieter, Live-Flag, ein Boolean pro Anbieter und ein Hinweis wie die ersten 3 und letzten 4 Zeichen. Das Einstellungsformular erhält den gespeicherten Schlüssel nie; ein unberührtes Feld beim Speichern bedeutet, den gespeicherten Schlüssel zu behalten (Flag keyTouched).

Bestehende Benutzer hatten Klartext-Schlüssel in einem alten SpeicherformatBeim ersten Lesen werden Legacy- und Klartext-Schlüssel ohne Zutun des Benutzers verschlüsselt neu gespeichert

sttGetAi() überführt den alten einzelnen gptApiKey in die Struktur pro Anbieter, verschlüsselt jeden Wert ohne das Präfix stt1:, schreibt das Ergebnis zurück und entfernt die Legacy-Zelle. Scheitert der Worker beim Verschlüsseln, bleibt der Klartext erhalten, damit der Benutzer nicht ausgesperrt wird, und die Migration wird beim nächsten Lesen erneut versucht.

sttDecrypt() gibt bei einem beschädigten oder fremden Wert einen leeren String zurück, sodass sich ein defekter Datensatz wie „kein Schlüssel“ verhält, statt eine Exception zu werfen.

Einstellungen müssen in jedem offenen Tab aktuell bleibenstorage.onChanged sendet die bereinigte Konfiguration erneut an das Seitenabbild jedes Tabs

Das Panel jedes Tabs hält ein lokales Abbild von Anbieter, Live-Flag und hasKeys. content.js hört auf chrome.storage.onChanged für den Datensatz sttAi und schiebt eine frische bereinigte Konfiguration in die Seite, sodass liveEnabled() nie auf einen Schlüssel reagiert, der anderswo gelöscht wurde.

Das Löschen eines Schlüssels im Popover ist bis zum Speichern lokal; Schließen ohne Speichern behält den gespeicherten Schlüssel.

Lauffähig in jeder Seite

im Produkt

Was es bringtDer Hotkey funktioniert auch in Tabs, die schon vor dem Laden der Erweiterung offen waren, und das Panel spricht auf jeder Website die Sprache des Benutzers.

Problem
Das Panel lebt in Seiten von Drittanbietern: Content Scripts können fehlen, und Code in der Seitenwelt hat keine chrome.*-APIs.
Lösung

Injektion bei Bedarf mit einem Fallback-Pfad und eine Lokalisierungsbrücke in den Seitenspeicher.

Stack
  • chrome.scripting
  • chrome.i18n
Aufgaben · 2
Hotkey in einem Tab gedrückt, in dem das Content Script nicht geladen istsendMessage versuchen; bei lastError content.js mit chrome.scripting injizieren, dann erneut senden

start() im Service Worker sendet den Toggle an den Tab. Zeigt chrome.runtime.lastError, dass niemand zuhört, injiziert es content.js und wiederholt die Nachricht. Das Panel-Modul und das Stylesheet werden dann auf Anforderung injiziert, wobei chrome://-Seiten übersprungen werden.

Das Popup prüft auf dieselbe Weise mit einem isLoaded-Ping, bevor es entscheidet, ob es dem Tab eine Nachricht sendet oder den Weg über den Worker nimmt.

Panel-Code in der Seitenwelt kann chrome.i18n nicht aufrufenDas Content Script kopiert jede Nachricht in den localStorage der Seite; en liefert die Key-Liste

getLocalizationKeys() liest die Key-Liste aus der messages.json der Standard-Locale, die den vollständigen Satz enthält, und speichert dann chrome.i18n.getMessage(key) für jeden unter einem Präfix stt_. Helper.getI18nMessage() in der Seite liest von dort.

Weil die Key-Liste aus en stammt und die Werte aus chrome.i18n, fällt eine Locale mit fehlenden Keys pro Key auf Englisch zurück. Der Locale hi fehlen derzeit 7 Keys, die auf diese Weise abgedeckt werden.

Zahlen

aus dem Produkt
  • Benutzer etwa 2,000 Chrome Web Store.
  • Oberflächensprachen 17
  • Diktiersprachen 33 63 regionale Varianten.