Język

Napisz do mnie

Rozszerzenie Chrome · od 2024

Voice Master

Rozszerzenie Chrome, które wpisuje podyktowaną mowę do dowolnego pola tekstowego na dowolnej stronie, z opcjonalną korektą AI przez własny klucz API użytkownika.

Wydany
Użytkownicy
ok. 2000
Języki interfejsu
17
Cena
Bezpłatny
  • JavaScript (moduły ES)
  • Chrome Extensions Manifest V3
  • Web Speech API (webkitSpeechRecognition)
  • Web Crypto API (AES-GCM)
  • chrome.storage / chrome.scripting / chrome.i18n
  • API HTTP OpenAI, Anthropic, DeepSeek i Gemini
Chrome Web Store chromewebstore.google.com

O mnie

Voice Master wstrzykuje do bieżącej strony przeciągany panel, uruchamia Web Speech API Chrome i wstawia rozpoznany tekst w miejscu kursora ostatnio aktywnego pola: input, textarea, contenteditable lub TinyMCE. Do tego dochodzą panel dyktowania z własnym polem tekstowym, skróty klawiszowe, 33 języki dyktowania i interfejs w 17 językach.

Trudny jest sam silnik rozpoznawania. webkitSpeechRecognition w trybie ciągłym kończy sesje samodzielnie po pauzach i po kilku minutach pracy, a zgłasza to wyłącznie przez onend. Rozszerzenie utrzymuje sesję dyktowania przy życiu osobnym watchdogiem, który wskrzesza zatrzymany silnik i restartuje działający według timera, dostarczając oczekujący tekst do pola przed każdym planowanym restartem.

Opcjonalna korekta AI wysyła podyktowane fragmenty do OpenAI, Anthropic, DeepSeek lub Gemini z kluczem należącym do użytkownika. Wywołania wychodzą z service workera, wyniki są wstawiane w kolejności wypowiedzenia, a nieudane wywołanie wraca do surowego tekstu. Zapisane klucze są szyfrowane w spoczynku i nie są wysyłane do JavaScriptu strony.

Voice Master · mowa na tekst

co rozwiązano

AKTYWNYod 2024

Utrzymywanie rozpoznawania przy życiu

w produkcie

Co to dajeDługie dyktowanie nie zatrzymuje się samo po pauzie ani po kilku minutach, a nic powiedziane tuż przed restartem nie ginie.

Problem
Ciągłe rozpoznawanie mowy w Chrome kończy sesje samo po pauzach i po kilku minutach, bez wiarygodnego sygnału poza onend. Długie dyktowanie po cichu się zatrzymuje.
Rozwiązanie

Osobna klasa Heartbeat odpytuje stan silnika co 100 ms, wskrzesza zatrzymany silnik po okresie karencji i restartuje działający według timera, a host dostarcza oczekujący tekst przed każdym planowanym restartem.

Technologie
  • Web Speech API
  • JavaScript
Zadania · 4
Silnik zatrzymuje się sam po pauzie i dyktowanie umieraWatchdog tyka co 100 ms i restartuje silnik po 1.5 s potwierdzonego przestoju

Heartbeat.tick() pyta hosta, czy rozpoznawanie działa. Gdy nie działa, zapisuje moment śmierci i czeka 1.5 s okresu karencji, zanim wywoła onRevive(), aby normalny cykl stop/start wewnątrz Chrome nie był zakłócany.

Naraz w toku jest tylko jedno wskrzeszenie. Jeśli onstart nie nadejdzie w ciągu 3 s, oczekujące wskrzeszenie jest porzucane i przed następną próbą zaczyna się nowy 1.5 s okres karencji. Flaga recognizing jest ustawiana wyłącznie przez onstart za pośrednictwem noteEngineUp(), nigdy optymistycznie, więc nieudane start() (InvalidStateError) nie może sprawić, by watchdog uwierzył, że silnik działa.

Chrome kończy długą sesję sam, w chwili, którą sam wybieraOdświeżanie co 270 s: dostarczyć oczekujący tekst, zatrzymać silnik, pozwolić watchdogowi go wskrzesić

Watchdog zapamiętuje czas startu bieżącej żywej podsesji. Po sessionMaxMs (270000 ms, ustawione w speech.js) wywołuje refreshSession(), które najpierw dostarcza zgromadzoną końcową transkrypcję i ostatnią frazę tymczasową do pola docelowego, a potem zatrzymuje silnik. end() widzi, że zatrzymanie nie było ręczne, i wraca bez finalizowania; kolejne tyknięcia wskrzeszają świeżą sesję.

Interwał dobrano tak, by wypadał przed mniej więcej pięciominutowym limitem, który autor zaobserwował w Chrome (podanym w komentarzach w kodzie), więc restart następuje w chwili kontrolowanej przez rozszerzenie, a nie wybranej przez Chrome.

Automatyczny restart nie może walczyć z użytkownikiem ani z odmówionym mikrofonemOsobne flagi manualStop i fatalError bramkują każde wskrzeszenie; tylko błędy mikrofonu i uprawnień zatrzymują je na dobre

onend odpala przy każdym rodzaju zatrzymania, więc end() nie potrafi ich sam rozróżnić. Host trzyma dwie flagi: manualStop jest ustawiana wyłącznie przez zatrzymanie użytkownika (przycisk, skrót klawiszowy, zamknięcie panelu), fatalError tylko przez błędy audio-capture i not-allowed. Watchdog i reviveRecognition() sprawdzają obie i pozostają bezczynne, gdy któraś jest ustawiona; każdy inny błąd, taki jak no-speech, zostawia sesję do wskrzeszenia.

Dla not-allowed handler porównuje czas błędu z czasem kliknięcia: błąd w ciągu 100 ms oznacza, że uprawnienie jest zablokowane polityką, późniejszy oznacza, że użytkownik odrzucił monit, a panel pokazuje inny komunikat dla każdego z tych przypadków.

Logika restartu splątana z surową obsługą Web Speech APICienka nakładka RecognitionEngine emituje callbacki; cały stan restartu zostaje w jednym hoście

RecognitionEngine posiada wyłącznie obiekt webkitSpeechRecognition (continuous, wyniki tymczasowe, 5 alternatyw) i parsuje wyniki na finalText, interimText i hadFinal. start() celowo pozwala InvalidStateError propagować, aby wywołujący zdecydował, czy ponowić.

Flagi, akumulacja i decyzje o restarcie żyją w speech.js, a polityka czasowa w Heartbeat. Te trzy części rozmawiają przez małe interfejsy callbacków (isRecognizing, onRevive, onRefresh, onSilence), więc watchdoga można stroić bez dotykania kodu rozpoznawania.

Wprowadzanie tekstu do dowolnego pola

w produkcie

Co to dajeWypowiedziane słowa lądują tam, gdzie był kursor, w dowolnym polu tekstowym lub edytorze na witrynie, a każda fraza jest wpisywana dopiero po zakończeniu.

Problem
Rozpoznany tekst musi trafić w miejsce kursora w tym polu, w którym użytkownik pisał, na stronach, których rozszerzenie nie kontroluje, podczas gdy użytkownik klika własny panel rozszerzenia.
Rozwiązanie

TextInserter śledzi ostatni kwalifikujący się aktywny element i używa metody wstawiania, którą akceptuje dany typ edytora; panel unika kradzieży fokusu.

Technologie
  • DOM
  • Selection API
  • document.execCommand
  • TinyMCE API
Zadania · 3
Pola się różnią: input, textarea, contenteditable, TinyMCEJedno insert() wybiera splice, execCommand('insertText') lub mceInsertContent według typu celu

Dla input i textarea wartość jest sklejana między selectionStart i selectionEnd, a kursor przesuwany za nowy tekst, więc zaznaczony fragment zostaje zastąpiony podyktowanym. Dla contenteditable używa execCommand('insertText'), które też zastępuje bieżące zaznaczenie i pozwala edytorowi zobaczyć zwykłą edycję. Dla TinyMCE wywołuje własne polecenie edytora mceInsertContent.

TinyMCE inicjalizuje się późno, więc hook fokusu dla niego jest podpinany z opóźnieniem i, jeśli nic innego nie było jeszcze aktywne, aktywny edytor dostaje fokus i jest śledzony.

Kliknięcie panelu odbiera fokus polu docelowemuŚledzić fokus w fazie przechwytywania, zachować ostatnie kwalifikujące się pole; blokować kradzież fokusu na kontrolkach

Nasłuchiwacz fokusu w fazie przechwytywania na document zapisuje każdy kwalifikujący się element na liście od najstarszego do najnowszego, więc gdy użytkownik klika panel, celem wstawiania nadal jest ostatnie pole, w którym był.

Przycisk startu anuluje mousedown, a selecty języka anulują mouseup, więc ich naciśnięcie nie odbiera fokusu polu. Przeciągany panel pomija obsługę przeciągania na polach, przyciskach i linkach, aby zachowały normalny fokus i zachowanie przy kliknięciu.

Wyniki tymczasowe zmieniają się bez przerwy i nie wolno ich wpisywać za wcześniePokazywać tekst tymczasowy w podglądzie; dostarczać frazę dopiero, gdy strumień tymczasowy się opróżni

Wyniki końcowe gromadzą się w finalTranscript. Tekst tymczasowy trafia do osobnego przeciąganego panelu podglądu i jest zapamiętywany jako prev_text; gdy przychodzi aktualizacja z pustą częścią tymczasową, buffer() dostarcza zapamiętaną frazę do pola i czyści podgląd.

Zakres końcowy jest renderowany ponownie tylko wtedy, gdy przyszedł nowy wynik końcowy, co ogranicza pracę na DOM podczas długiego dyktowania.

Potok korekty AI

w produkcie

Co to dajeAI wybrane przez użytkownika może poprawiać źle usłyszane słowa na podstawie kontekstu. Tekst zachowuje kolejność wypowiedzenia, a jeśli AI zawiedzie, surowy tekst i tak dociera.

Problem
Czterech dostawców AI z różnymi API, wywoływanych z wnętrza dowolnych stron z własnym CSP, podczas gdy dyktowanie stale produkuje nowe fragmenty, które muszą zachować kolejność.
Rozwiązanie

Adaptery dostawców działają w service workerze; strona rozmawia z nimi przez skorelowany most zdarzeń, a kolejka FIFO z limitem czasu i powrotem do surowego tekstu zasila pole.

Technologie
  • Service worker
  • fetch
  • CustomEvent
  • Promises
Zadania · 5
CSP i CORS strony blokują wywołania API AI ze stronyWszystkie wywołania API działają w service workerze MV3 z host_permissions dla czterech hostów

Korektor po stronie strony tylko buduje prompt. Żądanie podróżuje przez content script do service workera, który posiada fetch. host_permissions w manifest.json obejmują api.openai.com, api.anthropic.com, api.deepseek.com i generativelanguage.googleapis.com.

Nasłuchiwacz onMessage workera zwraca true, aby utrzymać otwarty kanał dla odpowiedzi asynchronicznej.

Czterech dostawców różni się uwierzytelnianiem oraz kształtem żądań i odpowiedziTabela adapterów: dla każdego dostawcy URL, budowniczy nagłówków, budowniczy ciała, parser i czytnik błędów

AI_PROVIDERS opisuje każde API deklaratywnie: nagłówek Bearer dla OpenAI i DeepSeek, x-api-key plus nagłówki wersji dla Anthropic, x-goog-api-key dla Gemini oraz ścieżkę do tekstu w każdej odpowiedzi. aiCorrect() jest takie samo dla wszystkich.

Pusty klucz zwraca kod 'no_key' zamiast błędu, co pozwala wywołującym po cichu wrócić do surowego tekstu. Gemini dodano jako czwartego dostawcę jednym wpisem w tabeli plus uprawnieniem hosta w manifeście.

Asynchroniczne odpowiedzi AI mogłyby wstawiać fragmenty poza kolejnością wypowiedzeniaKolejka FIFO jako łańcuch obietnic, limit czasu 20 s na fragment, surowy tekst wstawiany przy każdej awarii

LiveImprover.push() dopisuje każdy fragment do jednego łańcucha obietnic, więc fragment N+1 jest wysyłany dopiero po wstawieniu fragmentu N. Każde wywołanie ściga się z limitem czasu 20 s. Przy awarii lub przekroczeniu limitu wstawiany jest oryginalny tekst, więc podyktowana mowa nigdy nie ginie.

Modele zwracają tekst przycięty, więc keepSpacing() przywraca początkowe i końcowe odstępy fragmentu, aby kolejne fragmenty się nie sklejały. Bez klucza kolejka zgłasza kod 'no_key', a interfejs milczy, jakby tryb na żywo był wyłączony.

Świat strony i świat rozszerzenia mogą wymieniać tylko zdarzeniaID żądań i mapa oczekujących obietnic zamieniają zdarzenia okna w wywołania asynchroniczne

Kod panelu działa jako skrypt modułowy na stronie i nie ma API chrome.*. GptCorrector.improve() przydziela id sekwencyjne, zapisuje resolver w Map i wysyła stt_ai_correct_request. Content script przekazuje to workerowi i odpowiada stt_ai_correct_response z tym samym id, co rozstrzyga dokładnie jedną oczekującą obietnicę.

Silnik mowy przekręca słowa; zwykła kontrola pisowni ich nie poprawiaZlokalizowany prompt mówi modelowi, że tekst to surowe STT i że ma poprawiać słowa na podstawie kontekstu

Prompt corr_and_return stwierdza, że tekst pochodzi z niedoskonałego rozpoznawania mowy, prosi model o zastąpienie błędnie rozpoznanych słów tym, co mówiący najprawdopodobniej miał na myśli, o zachowanie języka i znaczenia, niedodawanie niczego i zwrócenie wyłącznie tekstu. Jest przetłumaczony na każde ustawienie regionalne interfejsu.

W panelu dyktowania poprawiony tekst zastępuje zawartość textarea przez execCommand('insertText'), więc Ctrl+Z przywraca oryginalną wersję.

Obsługa kluczy API

shipped · 2026

Co to dajeWłasne klucze AI użytkownika są przechowywane zaszyfrowane, a witryny, na których pojawia się panel, nie mogą ich odczytać. Ustawienia pozostają takie same w każdej otwartej karcie.

Problem
Klucze API użytkownika musi przechowywać rozszerzenie, którego interfejs działa wewnątrz niezaufanych stron, więc dowolny skrypt strony mógłby odczytać to, co może odczytać panel.
Rozwiązanie

Klucze są szyfrowane i odszyfrowywane w service workerze, trzymane jawnym tekstem tylko w pamięci content scriptu, a strona dostaje wyłącznie flagi i zamaskowane podpowiedzi.

Technologie
  • Web Crypto API (AES-GCM)
  • chrome.storage.local
  • Service worker
Zadania · 4
Klucze API nie mogą leżeć jawnym tekstem w magazynie rozszerzeniaAES-GCM z losowym 256-bitowym sekretem dla instalacji i świeżym 12-bajtowym IV na wartość

sttCryptoKey() generuje sekret raz, zapisuje go i importuje jako nieeksportowalny CryptoKey. Zaszyfrowane wartości niosą prefiks wersji (stt1:), po którym następują IV i szyfrogram w base64.

Kryptografia działa w service workerze, ponieważ jest on zawsze bezpiecznym kontekstem z crypto.subtle, a content script na zwykłej stronie http nie jest. Komentarz w kodzie wyraźnie mówi, że to ochrona w spoczynku przed zwykłym odczytem, a nie przed kimś, kto ma cały profil przeglądarki.

Skrypty strony nie mogą mieć możliwości odczytu zapisanych kluczyZdarzenia strony niosą tylko flagi hasKeys i zamaskowane podpowiedzi; jawny tekst zostaje w content scripcie

sttPublicCfg() jest jedynym kształtem przechodzącym na stronę: dostawca, flaga live, wartość logiczna dla dostawcy i podpowiedź w rodzaju pierwszych 3 i ostatnich 4 znaków. Formularz ustawień nigdy nie dostaje zapisanego klucza; nietknięte pole przy Zapisz oznacza zachowanie zapisanego klucza (flaga keyTouched).

Istniejący użytkownicy mieli klucze jawnym tekstem w starym formacie przechowywaniaPrzy pierwszym odczycie klucze dawne i jawne są zapisywane ponownie zaszyfrowane bez działania użytkownika

sttGetAi() przenosi stary pojedynczy gptApiKey do struktury dla dostawców, szyfruje każdą wartość bez prefiksu stt1:, zapisuje wynik z powrotem i usuwa dawną komórkę. Jeśli worker nie zdoła zaszyfrować, jawny tekst jest zachowywany, aby użytkownik nie został zablokowany, a migracja jest ponawiana przy następnym odczycie.

sttDecrypt() zwraca pusty ciąg dla uszkodzonej lub obcej wartości, więc zepsuty rekord zachowuje się jak 'brak klucza' zamiast rzucać wyjątek.

Ustawienia muszą być aktualne w każdej otwartej karciestorage.onChanged ponownie rozsyła oczyszczoną konfigurację do lustra strony w każdej karcie

Panel każdej karty trzyma lokalne lustro dostawcy, flagi live i hasKeys. content.js nasłuchuje chrome.storage.onChanged dla rekordu sttAi i wypycha na stronę świeżą, oczyszczoną konfigurację, więc liveEnabled() nigdy nie działa na kluczu, który został usunięty gdzie indziej.

Usunięcie klucza w popoverze jest lokalne do czasu Zapisz; zamknięcie bez zapisu zachowuje zapisany klucz.

Działanie wewnątrz dowolnej strony

w produkcie

Co to dajeSkrót klawiszowy działa nawet w kartach otwartych przed załadowaniem rozszerzenia, a panel mówi językiem użytkownika na każdej witrynie.

Problem
Panel żyje wewnątrz stron osób trzecich: content scripts mogą nie być załadowane, a kod w świecie strony nie ma API chrome.*.
Rozwiązanie

Wstrzykiwanie na żądanie ze ścieżką awaryjną i mostem lokalizacji do magazynu strony.

Technologie
  • chrome.scripting
  • chrome.i18n
Zadania · 2
Skrót naciśnięty w karcie, w której content script nie jest załadowanySpróbować sendMessage; przy lastError wstrzyknąć content.js przez chrome.scripting, potem wysłać ponownie

start() w service workerze wysyła przełączenie do karty. Jeśli chrome.runtime.lastError pokazuje, że nikt nie nasłuchuje, wstrzykuje content.js i powtarza wiadomość. Moduł panelu i arkusz stylów są następnie wstrzykiwane na żądanie, z pominięciem stron chrome://.

Popup sprawdza to samo przez ping isLoaded, zanim zdecyduje, czy wysłać wiadomość do karty, czy iść przez workera.

Kod panelu w świecie strony nie może wywołać chrome.i18nContent script kopiuje każdą wiadomość do localStorage strony; en dostarcza listę kluczy

getLocalizationKeys() czyta listę kluczy z messages.json domyślnych ustawień regionalnych, który ma pełny zestaw, a następnie zapisuje chrome.i18n.getMessage(key) dla każdego pod prefiksem stt_. Helper.getI18nMessage() na stronie czyta stamtąd.

Ponieważ lista kluczy pochodzi z en, a wartości z chrome.i18n, wersja z brakującymi kluczami wraca do angielskiego dla każdego klucza osobno. W wersji hi brakuje obecnie 7 kluczy i jest to w ten sposób pokryte.

Liczby

z produktu
  • Użytkownicy około 2,000 Chrome Web Store.
  • Języki interfejsu 17
  • Języki dyktowania 33 63 warianty regionalne.