Ditado por voz local para Windows, com transcrição via Whisper e revisão inteligente via Qwen — feito em C#/.NET e WPF.
C#
0
2 commits
updated Sep 10, 2026
Ditado por voz local para Windows. Você segura uma tecla, fala, e o texto aparece no campo onde já estava digitando.
Tudo roda na sua máquina. Sem API externa, sem nuvem, sem Ollama, sem Python, sem telemetria. O áudio nunca sai do processo e nunca toca o disco.
Ctrl + Espaço (segurar)
│
├── captura a janela ativa
├── grava pelo WASAPI, mono 16 kHz, em memória
│
(soltar)
│
├── whisper.cpp ──► transcrição
│
├── Modo Literal ──► texto direto
└── Modo Inteligente ──► llama.cpp + Qwen ──► texto revisado
│
├── devolve o foco à janela original
└── SendInput ou clipboard
│
▼
Outlook · Chrome · Edge · WhatsApp · PhpStorm
VS Code · Bloco de Notas · Word · Slack · Teams
Funciona de ponta a ponta e foi medido nesta máquina, numa RTX 3070 com áudio de nove segundos em português:
| Etapa | Backend | Tempo |
|---|---|---|
| Transcrição (Whisper large-v3-turbo q5_0) | Vulkan | 361 ms |
| Revisão (Qwen3-4B-Instruct Q4_K_M) | Vulkan | 472 ms |
| Inserção por SendInput, 170 caracteres | Win32 | 155 ms |
| Inserção por clipboard, 1967 caracteres | Win32 | 965 ms |
Entrada e saída do teste de aceitação:
falado : "Olá Lucas bom dia verificamos aqui e aparentemente
o email já está funcionando poderia fazer um novo teste"
literal : "Olá Lucas, bom dia, verificamos aqui e aparentemente
o e-mail já está funcionando, poderia fazer um novo teste."
revisado : "Olá Lucas, bom dia. Verificamos aqui e aparentemente
o e-mail já está funcionando. Poderia fazer um novo teste?"
Placa NVIDIA, AMD ou Intel servem: o backend usa Vulkan, que acompanha o driver de vídeo. Não é preciso instalar CUDA Toolkit. Veja a seção sobre GPU mais abaixo.
dotnet build
dotnet test
dotnet run --project src/DigitaIA.App
Para gerar a versão distribuível:
dotnet publish src/DigitaIA.App -c Release -r win-x64 --self-contained true
A saída fica com cerca de 375 MB, quase toda em bibliotecas nativas de
inferência. Os modelos ficam de fora, em %LOCALAPPDATA%.
O projeto traz um NuGet.Config próprio apontando para o nuget.org, porque
a configuração global desta máquina estava sem fontes.
Nada é embutido no executável. Coloque os arquivos em:
%LOCALAPPDATA%\DigitaIA\Models\
├── Whisper\ggml-large-v3-turbo-q5_0.bin 574 MB
└── LLM\Qwen3-4B-Instruct-2507-Q4_K_M.gguf 2,4 GB
O app cria essas pastas na primeira execução e avisa quando um modelo falta, sem quebrar. O Modo Literal funciona só com o Whisper; o modelo de linguagem é necessário apenas para o Modo Inteligente.
curl.exe -L -o "$env:LOCALAPPDATA\DigitaIA\Models\Whisper\ggml-large-v3-turbo-q5_0.bin" `
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin
O large-v3-turbo quantizado em q5_0 é o melhor equilíbrio para português
brasileiro numa placa de 8 GB. Qualquer ggml-*.bin na pasta serve; se o
arquivo recomendado não existir, o app usa o primeiro que encontrar.
curl.exe -L -o "$env:LOCALAPPDATA\DigitaIA\Models\LLM\Qwen3-4B-Instruct-2507-Q4_K_M.gguf" `
https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen3-4B-Instruct-2507-Q4_K_M.gguf
A variante Instruct-2507 não emite blocos de raciocínio, então a saída já
vem limpa. Qualquer .gguf na pasta serve.
O app usa Vulkan para acelerar Whisper e Qwen. Vulkan precisa apenas do
vulkan-1.dll, que vem junto com o driver de vídeo, então funciona sem
instalar nada.
CUDA está antes do Vulkan na ordem de carregamento: se a máquina tiver o CUDA Toolkit instalado, ele é usado; se não, a tentativa falha e o app segue no Vulkan, depois em CPU. Nunca quebra por falta de GPU.
Os pacotes CUDA do whisper.cpp e do llama.cpp não embutem o runtime
CUDA, e por isso não foram distribuídos aqui. O raciocínio completo, com a
tabela de dependências que mostra isso, está em
docs/DECISIONS.md.
O backend em uso aparece na janela principal, em "Processamento", e no log.
VRAM ocupada com os dois modelos carregados: cerca de 3,8 GB.
%LOCALAPPDATA%\DigitaIA\settings.json, criado com padrões na primeira
execução:
{
"mode": "Literal",
"hotkey": "Ctrl+Space",
"language": "pt",
"whisperModel": "default",
"llmModel": "default",
"gpuEnabled": true,
"llmGpuLayers": -1,
"llmContextSize": 4096,
"audioDeviceId": "",
"clipboardInjectionThreshold": 600,
"restoreClipboard": true,
"minimumRecordingMilliseconds": 300,
"logTranscripts": false,
"showOverlay": true,
"blockHotkeyPassthrough": true
}
Campos que merecem explicação:
hotkey aceita combinações como Ctrl+Space, Ctrl+Alt+Space,
F9, ou um modificador solto como RightCtrl.blockHotkeyPassthrough consome a combinação para o autocompletar do
PhpStorm e do VS Code não disparar enquanto você fala. Desligue se
preferir manter o atalho original desses editores.language usa código curto (pt, en, es) ou auto para
detecção automática.clipboardInjectionThreshold é o tamanho a partir do qual a inserção
passa a usar clipboard em vez de digitar caractere a caractere.logTranscripts liga o registro do texto ditado no log. Desligado
por padrão, por privacidade.llmGpuLayers com -1 tenta colocar tudo na GPU. Reduza se a VRAM
apertar.Um arquivo corrompido ou incompleto não derruba o app: os campos ausentes voltam ao padrão.
DigitaIA.sln
Directory.Build.props TFM, versões de pacote, propriedades comuns
Directory.Build.targets implicit usings que o SDK do WPF descarta
src/
├── DigitaIA.Core contratos, estados, configuração, pipeline
│ net10.0 puro, sem nada de Windows
├── DigitaIA.Windows hook de teclado, SendInput, clipboard, janela ativa
├── DigitaIA.Audio captura WASAPI e conversão para o formato do Whisper
├── DigitaIA.Transcription Whisper.net
├── DigitaIA.AI LLamaSharp e prompts
└── DigitaIA.App WPF, bandeja, overlay, injeção de dependência
tests/
├── DigitaIA.Core.Tests pipeline, estados, configuração, prompts (80)
└── DigitaIA.Audio.Tests conversão e reamostragem de áudio (15)
docs/DECISIONS.md decisões de arquitetura e o que foi medido
DigitaIA.Core não referencia nenhum outro projeto. É isso que permite
testar o pipeline inteiro com mocks, sem tocar em Win32 nem carregar
modelos.
| Tipo | Papel |
|---|---|
DictationPipeline | orquestra o ciclo, sem saber que existe UI |
StateMachine | recusa transições inválidas e gravações concorrentes |
LowLevelHotkeyService | WH_KEYBOARD_LL com key-down e key-up |
WasapiAudioCaptureService | captura em memória e aquecimento do dispositivo |
WhisperTranscriptionService | modelo residente, backend reportado |
LlamaTextEnhancementService | StatelessExecutor, revisão de turno único |
CompositeTextInjectionStrategy | escolhe SendInput ou clipboard pelo tamanho |
ActiveWindowService | fotografa e restaura a janela do usuário |
O app vive na bandeja. Clicar no ícone abre uma janela pequena com status, modo, atalho, modelo e backend. Fechar essa janela apenas esconde; sair de vez é pelo menu de contexto do ícone.
O ícone muda de cor conforme o estado: azul parado, vermelho gravando, laranja processando.
Durante o ditado aparece um overlay discreto no canto inferior direito, com
🎙 Fala aí..., Transcrevendo..., Revisando... e ✓ Pronto. Ele nunca
recebe foco e some sozinho.
%LOCALAPPDATA%\DigitaIA\Logs\digitaia-AAAAMMDD.log, com rotação diária e
14 dias de retenção.
Registra início e fim de gravação, duração do áudio, tempo de transcrição, tempo do LLM, tempo de inserção, processo da janela ativa, backend e erros.
Não registra áudio, nunca. Não registra o texto ditado, a menos que
logTranscripts seja ligado.
settings.json. Trocar de modo funciona pela bandeja e pela janela
principal.IModelManager já prevê
download com progresso e checksum, mas DownloadAsync lança
NotSupportedException. Baixe os arquivos manualmente.settings.json aceita audioDeviceId, mas não há tela.Não existe telemetria, analytics, Sentry, nem chamada a serviço de IA remoto. Não há código de rede no projeto fora do downloader ainda não implementado.
Áudio, transcrição e texto revisado ficam na memória do processo e vão apenas para a janela que você escolheu.
C#
100.0%
Ditado por voz local para Windows, com transcrição via Whisper e revisão inteligente via Qwen — feito em C#/.NET e WPF.
C#
0
2 commits
updated Sep 10, 2026
Ditado por voz local para Windows. Você segura uma tecla, fala, e o texto aparece no campo onde já estava digitando.
Tudo roda na sua máquina. Sem API externa, sem nuvem, sem Ollama, sem Python, sem telemetria. O áudio nunca sai do processo e nunca toca o disco.
Ctrl + Espaço (segurar)
│
├── captura a janela ativa
├── grava pelo WASAPI, mono 16 kHz, em memória
│
(soltar)
│
├── whisper.cpp ──► transcrição
│
├── Modo Literal ──► texto direto
└── Modo Inteligente ──► llama.cpp + Qwen ──► texto revisado
│
├── devolve o foco à janela original
└── SendInput ou clipboard
│
▼
Outlook · Chrome · Edge · WhatsApp · PhpStorm
VS Code · Bloco de Notas · Word · Slack · Teams
Funciona de ponta a ponta e foi medido nesta máquina, numa RTX 3070 com áudio de nove segundos em português:
| Etapa | Backend | Tempo |
|---|---|---|
| Transcrição (Whisper large-v3-turbo q5_0) | Vulkan | 361 ms |
| Revisão (Qwen3-4B-Instruct Q4_K_M) | Vulkan | 472 ms |
| Inserção por SendInput, 170 caracteres | Win32 | 155 ms |
| Inserção por clipboard, 1967 caracteres | Win32 | 965 ms |
Entrada e saída do teste de aceitação:
falado : "Olá Lucas bom dia verificamos aqui e aparentemente
o email já está funcionando poderia fazer um novo teste"
literal : "Olá Lucas, bom dia, verificamos aqui e aparentemente
o e-mail já está funcionando, poderia fazer um novo teste."
revisado : "Olá Lucas, bom dia. Verificamos aqui e aparentemente
o e-mail já está funcionando. Poderia fazer um novo teste?"
Placa NVIDIA, AMD ou Intel servem: o backend usa Vulkan, que acompanha o driver de vídeo. Não é preciso instalar CUDA Toolkit. Veja a seção sobre GPU mais abaixo.
dotnet build
dotnet test
dotnet run --project src/DigitaIA.App
Para gerar a versão distribuível:
dotnet publish src/DigitaIA.App -c Release -r win-x64 --self-contained true
A saída fica com cerca de 375 MB, quase toda em bibliotecas nativas de
inferência. Os modelos ficam de fora, em %LOCALAPPDATA%.
O projeto traz um NuGet.Config próprio apontando para o nuget.org, porque
a configuração global desta máquina estava sem fontes.
Nada é embutido no executável. Coloque os arquivos em:
%LOCALAPPDATA%\DigitaIA\Models\
├── Whisper\ggml-large-v3-turbo-q5_0.bin 574 MB
└── LLM\Qwen3-4B-Instruct-2507-Q4_K_M.gguf 2,4 GB
O app cria essas pastas na primeira execução e avisa quando um modelo falta, sem quebrar. O Modo Literal funciona só com o Whisper; o modelo de linguagem é necessário apenas para o Modo Inteligente.
curl.exe -L -o "$env:LOCALAPPDATA\DigitaIA\Models\Whisper\ggml-large-v3-turbo-q5_0.bin" `
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin
O large-v3-turbo quantizado em q5_0 é o melhor equilíbrio para português
brasileiro numa placa de 8 GB. Qualquer ggml-*.bin na pasta serve; se o
arquivo recomendado não existir, o app usa o primeiro que encontrar.
curl.exe -L -o "$env:LOCALAPPDATA\DigitaIA\Models\LLM\Qwen3-4B-Instruct-2507-Q4_K_M.gguf" `
https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen3-4B-Instruct-2507-Q4_K_M.gguf
A variante Instruct-2507 não emite blocos de raciocínio, então a saída já
vem limpa. Qualquer .gguf na pasta serve.
O app usa Vulkan para acelerar Whisper e Qwen. Vulkan precisa apenas do
vulkan-1.dll, que vem junto com o driver de vídeo, então funciona sem
instalar nada.
CUDA está antes do Vulkan na ordem de carregamento: se a máquina tiver o CUDA Toolkit instalado, ele é usado; se não, a tentativa falha e o app segue no Vulkan, depois em CPU. Nunca quebra por falta de GPU.
Os pacotes CUDA do whisper.cpp e do llama.cpp não embutem o runtime
CUDA, e por isso não foram distribuídos aqui. O raciocínio completo, com a
tabela de dependências que mostra isso, está em
docs/DECISIONS.md.
O backend em uso aparece na janela principal, em "Processamento", e no log.
VRAM ocupada com os dois modelos carregados: cerca de 3,8 GB.
%LOCALAPPDATA%\DigitaIA\settings.json, criado com padrões na primeira
execução:
{
"mode": "Literal",
"hotkey": "Ctrl+Space",
"language": "pt",
"whisperModel": "default",
"llmModel": "default",
"gpuEnabled": true,
"llmGpuLayers": -1,
"llmContextSize": 4096,
"audioDeviceId": "",
"clipboardInjectionThreshold": 600,
"restoreClipboard": true,
"minimumRecordingMilliseconds": 300,
"logTranscripts": false,
"showOverlay": true,
"blockHotkeyPassthrough": true
}
Campos que merecem explicação:
hotkey aceita combinações como Ctrl+Space, Ctrl+Alt+Space,
F9, ou um modificador solto como RightCtrl.blockHotkeyPassthrough consome a combinação para o autocompletar do
PhpStorm e do VS Code não disparar enquanto você fala. Desligue se
preferir manter o atalho original desses editores.language usa código curto (pt, en, es) ou auto para
detecção automática.clipboardInjectionThreshold é o tamanho a partir do qual a inserção
passa a usar clipboard em vez de digitar caractere a caractere.logTranscripts liga o registro do texto ditado no log. Desligado
por padrão, por privacidade.llmGpuLayers com -1 tenta colocar tudo na GPU. Reduza se a VRAM
apertar.Um arquivo corrompido ou incompleto não derruba o app: os campos ausentes voltam ao padrão.
DigitaIA.sln
Directory.Build.props TFM, versões de pacote, propriedades comuns
Directory.Build.targets implicit usings que o SDK do WPF descarta
src/
├── DigitaIA.Core contratos, estados, configuração, pipeline
│ net10.0 puro, sem nada de Windows
├── DigitaIA.Windows hook de teclado, SendInput, clipboard, janela ativa
├── DigitaIA.Audio captura WASAPI e conversão para o formato do Whisper
├── DigitaIA.Transcription Whisper.net
├── DigitaIA.AI LLamaSharp e prompts
└── DigitaIA.App WPF, bandeja, overlay, injeção de dependência
tests/
├── DigitaIA.Core.Tests pipeline, estados, configuração, prompts (80)
└── DigitaIA.Audio.Tests conversão e reamostragem de áudio (15)
docs/DECISIONS.md decisões de arquitetura e o que foi medido
DigitaIA.Core não referencia nenhum outro projeto. É isso que permite
testar o pipeline inteiro com mocks, sem tocar em Win32 nem carregar
modelos.
| Tipo | Papel |
|---|---|
DictationPipeline | orquestra o ciclo, sem saber que existe UI |
StateMachine | recusa transições inválidas e gravações concorrentes |
LowLevelHotkeyService | WH_KEYBOARD_LL com key-down e key-up |
WasapiAudioCaptureService | captura em memória e aquecimento do dispositivo |
WhisperTranscriptionService | modelo residente, backend reportado |
LlamaTextEnhancementService | StatelessExecutor, revisão de turno único |
CompositeTextInjectionStrategy | escolhe SendInput ou clipboard pelo tamanho |
ActiveWindowService | fotografa e restaura a janela do usuário |
O app vive na bandeja. Clicar no ícone abre uma janela pequena com status, modo, atalho, modelo e backend. Fechar essa janela apenas esconde; sair de vez é pelo menu de contexto do ícone.
O ícone muda de cor conforme o estado: azul parado, vermelho gravando, laranja processando.
Durante o ditado aparece um overlay discreto no canto inferior direito, com
🎙 Fala aí..., Transcrevendo..., Revisando... e ✓ Pronto. Ele nunca
recebe foco e some sozinho.
%LOCALAPPDATA%\DigitaIA\Logs\digitaia-AAAAMMDD.log, com rotação diária e
14 dias de retenção.
Registra início e fim de gravação, duração do áudio, tempo de transcrição, tempo do LLM, tempo de inserção, processo da janela ativa, backend e erros.
Não registra áudio, nunca. Não registra o texto ditado, a menos que
logTranscripts seja ligado.
settings.json. Trocar de modo funciona pela bandeja e pela janela
principal.IModelManager já prevê
download com progresso e checksum, mas DownloadAsync lança
NotSupportedException. Baixe os arquivos manualmente.settings.json aceita audioDeviceId, mas não há tela.Não existe telemetria, analytics, Sentry, nem chamada a serviço de IA remoto. Não há código de rede no projeto fora do downloader ainda não implementado.
Áudio, transcrição e texto revisado ficam na memória do processo e vão apenas para a janela que você escolheu.
C#
100.0%