Skip to content
/desenvolvimento-software 8 min read

Ensinar o Mac a escrever bem: ditado com Whisper e glossário

O ditado do macOS não aprende palavras. O Glossa põe o Whisper na tecla de ditado, com o teu glossário e o contexto do cursor, e os nomes saem certos.

O ditado da Apple transforma "vLLM na megamaschine" em "VLLM na Megamachine" todas as vezes, e não há maneira de lhe ensinar uma palavra. O Whisper aprende: recebe um pequeno prompt antes do áudio, e o que estiver nesse prompt define como escreve o que vem a seguir. Construí o Glossa, uma app de barra de menus para macOS que toma conta da tecla de ditado, envia a tua voz para um servidor Whisper com o teu glossário e o texto antes do cursor como prompt, e escreve o resultado. Do primeiro benchmark a uma release pública notarizada foi cerca de meia hora, numa só sessão de Claude Code.

Dito muito. Prompts para o Claude Code, mensagens no Slack, um email ou outro. Quase tudo cheio de nomes que só existem no meu mundo: hostnames, nomes de produtos, o modelo por trás de um alias.

O ditado do macOS trata bem do inglês. Só não consegue escrever nenhuma das minhas palavras.

Pedi ao Claude Code para explorar uma alternativa, e a exploração virou uma release antes do jantar.

Índice

O problema não é a precisão

Diz esta frase ao ditado do macOS: restart the vLLM container on megamaschine and point llm-router at sparkmaschine, then check the Tailscale MagicDNS entry for supermaschine.

Recebes de volta: Please restart the VLLM container on Megamachine and point the LLM router at Spark Machine. Then check the TailScale Magic DNS entry for SuperMachine.

Cada palavra é um palpite razoável. Cada uma está errada. Foneticamente a transcrição é perfeita, e mesmo assim tenho de corrigir sete coisas antes de poder enviar.

O ditado da Apple não tem vocabulário configurável. Apps como o Superwhisper e o VoiceInk têm um dicionário, o que ajuda, mas nenhuma sabe em que frase eu ia a meio.

O Whisper ouve um prompt

O Whisper da OpenAI tem uma funcionalidade que as apps de ditado usam quase só para uma coisa: um prompt inicial. É um pequeno texto que o modelo vê antes do áudio, como se fosse a transcrição até ali. O modelo continua-o.

Põe vocabulário no prompt e as palavras saem escritas assim. Põe a frase anterior e o modelo mantém o registo, a pontuação e as maiúsculas do que estavas a escrever.

Passei a mesma frase sintética pelo whisper.cpp no meu MacBook, com o modelo large-v3-turbo, antes de construir o que quer que fosse:

Sem prompt:   Please restart the VLLM container on Megamachine and point the LLM router at Spark Machine. Then check the TailScale Magic DNS entry for SuperMachine.
Com prompt:   Please restart the vLLM container on megamaschine and point the LLM router at sparkmaschine. Then check the Tailscale MagicDNS entry for supermaschine.

O prompt era uma linha: Terms: vLLM, megamaschine, sparkmaschine, supermaschine, llm-router, Tailscale, MagicDNS. Todos os termos saíram exatamente certos, e a chamada demorou cerca de um segundo só no portátil. Ficou decidido se valia a pena construir.

O que o Glossa faz

Glossa: a mesma frase pelo ditado do macOS e pelo Whisper com um glossário

O Glossa é uma app de barra de menus, cerca de 600 linhas de Swift, sem projeto Xcode.

  • Carrega na tecla do microfone do teclado para começar, carrega outra vez para parar, ou mantém-na premida para falar. Duplo toque no Fn e Fn premido também funcionam. Nada muda na forma como ditas.
  • Antes de gravar, lê o teu glossário, um ficheiro de texto com um termo por linha, e pede à app onde estás o texto antes do cursor através da API de Acessibilidade. Os dois vão para o prompt do Whisper, glossário primeiro, contexto no fim, para que o contexto sobreviva quando o prompt é demasiado longo.
  • O áudio vai para um servidor Whisper num único pedido HTTP. Por omissão é o whisper-server do whisper.cpp no teu próprio Mac, mantido quente como launch agent. Pode ser qualquer endpoint de transcrição compatível com a API da OpenAI na tua rede.
  • O resultado é colado onde estavas, e o que tinhas na área de transferência volta ao sítio.
  • Se estiver música a tocar quando começas, o volume desce dez pontos enquanto falas e volta quando paras.

Nada sai da máquina a não ser que o apontes para outro lado. O servidor é um ficheiro de modelo com 550 MB e um processo.

O Whisper não faz streaming, por isso não há texto ao vivo enquanto falas. Todas as apps Whisper "em tempo real" voltam a transcrever o áudio acumulado a cada segundo ou dois e mostram texto provisório numa janela flutuante. Está na lista; a colagem no fim ficaria igual.

A tecla de ditado não é o que eu pensava

A única coisa que eu queria era manter a tecla em que já carrego. Num MacBook é a tecla do microfone, F5, e o macOS reclama-a: com o Ditado desligado, carregar nela mostra um diálogo a perguntar se queres ligar o Ditado.

O macOS a perguntar se quer ativar o Ditado quando se carrega na tecla do microfone

Assumi que a tecla era uma tecla multimédia, como o volume e o brilho, que chegam como eventos definidos pelo sistema. Não é. Chega a um CGEvent tap como uma tecla normal, código virtual 176, com o modificador Fn ativo. Nada documenta isto; a app registou todos os eventos e eu carreguei na tecla.

Um event tap ativo pode engolir um evento devolvendo nada. O Glossa engole essa tecla, por isso o macOS nunca a vê e o diálogo nunca aparece. Carregar e largar alterna, manter premido é push-to-talk, e como o tap vê a tecla a descer e a subir, os dois saem do mesmo handler.

A permissão de Acessibilidade que torna o tap possível é a mesma que deixa o Glossa ler o texto antes do cursor e colar o resultado. Uma permissão, três usos.

Meia hora do benchmark à release

A sessão começou com "vamos explorar este pequeno projeto". Acabou com um build universal notarizado no GitHub.

Pelo meio, numa só sessão de Claude Code: o benchmark acima, um levantamento curto das apps existentes, a app Swift com o event tap, o gravador, o construtor do prompt e a colagem, um launch agent para o servidor, um bundle assinado para que as permissões do macOS sobrevivam a rebuilds, um README, changelog e pipeline de release iguais aos do Smart Terminal, e o repositório, a release e a entrada na página de ferramentas. A versão 0.2.0 veio vinte minutos depois, com a música a baixar e um cask do Homebrew.

A minha parte foi a mesma que no wims e no Smart Terminal: dizer o que devia ser, carregar nas teclas e relatar o que aconteceu. O agente não consegue carregar na minha tecla do microfone. Todos os testes do atalho fui eu, e o log que ele escrevia era a forma de descobrir o que eu tinha visto.

O que correu mal

O pedido de Acessibilidade não é um pedido. Disse ao Claude que tinha dado a permissão. Tinha dado o microfone. A Acessibilidade é um interruptor nas Definições do Sistema para onde o diálogo só aponta, e a app ficou à espera até o agente ler a base de dados de permissões e reparar.

Mudar o nome da app revogou tudo. Dictate passou a Glossa, o que significou um novo bundle identifier, que o macOS trata como uma app nova. O meu relatório de bug ditado, "we lost the shortcut override when updating to Glossa", está no log, transcrito sem erros.

O log de debug gravou o que eu escrevia. Registar todos os eventos de teclado para encontrar a tecla do microfone registou também os códigos da mensagem que escrevi a seguir. O log passou a guardar só teclas com código 96 ou superior, as teclas F e as especiais, e o primeiro log foi apagado.

Detetar a língua custa meio segundo. Os primeiros ditados a sério demoraram 1,5 a 1,9 segundos para clips de dois segundos, mais lentos do que o benchmark. A deteção de língua é uma passagem extra pelo encoder. Fixar em inglês trouxe uma frase para cerca de 0,6 segundos, e o prompt com o glossário não custa quase nada.

O Homebrew pede para confiar numa tap. O primeiro brew install a partir da tap nova parou num prompt que o filtro de output escondeu, e passei um minuto a perguntar-me porque é que nada tinha sido instalado.

Instalar

brew install --cask pluginslab/tap/glossa
glossa-server

O cask instala a app e o whisper.cpp; o glossa-server descarrega o modelo e arranca o launch agent. Há também um DMG notarizado para macOS 14 ou posterior, com o mesmo script do servidor dentro da app.

Depois dá as permissões de Acessibilidade e Microfone, e em Definições do Sistema → Teclado põe "Premir a tecla Globo para" em Não fazer nada e o atalho do Ditado em Desligado, para que o ditado da Apple deixe de responder à tecla.

O teu glossário está em ~/.config/glossa/glossary.txt. Põe lá o que o Whisper escrever mal, e edita-o sempre que apanhares uma palavra nova; é lido em cada ditado.

O Glossa é MIT e o código está no GitHub. Faz um pedido de rede por ditado, ao servidor que configuraste.

Se o teu Mac anda há um ano a escrever "Megamachine", a solução é esta.


Links: Glossa no GitHub · whisper.cpp · Prompting no Whisper · Smart Terminal · wims

Tags: macOS, Whisper, Ditado, ferramentas de developer