Agente de voz local acionado por atalho global. Escuta, transcreve, raciocina com tool-calling e responde em voz — rodando em background no Windows e no Linux, com uma overlay mínima que aparece só quando é chamada.
AltGr + R → 🎙 fala → VAD detecta o fim → STT → LLM (stream) → TTS (chunks) → 🔊
↑ │
└──────── falar por cima corta ────────┘
O projeto está escrito e completo, mas ainda não foi compilado — a máquina
onde ele foi montado não tem toolchain Rust. O frontend está verificado
(svelte-check limpo, vite build passando); o backend Rust precisa de uma
primeira rodada de cargo build para acertar o que a leitura não pega.
Ver Primeira compilação.
Tauri em vez de Electron: binário de alguns MB em vez de ~150, consumo de RAM
muito menor, e o mesmo repositório gera .exe, .AppImage e .deb. O que é
sistema — atalho global, microfone, processos, agenda — fica no lado Rust; a
webview só desenha.
src/ frontend (Svelte 5 + Vite)
App.svelte overlay: estado, transcrição, resposta
lib/Waveform.svelte indicador de áudio (canvas)
lib/Confirm.svelte diálogo de confirmação de ação destrutiva
lib/ipc.ts contrato tipado de eventos e comandos
src-tauri/src/
lib.rs bootstrap, bandeja, comandos expostos
session.rs máquina de estados do turno, barge-in
hotkey.rs atalho global no nível do SO
overlay.rs posicionamento e visibilidade da janela
config.rs config em TOML + resolução da chave
audio/
capture.rs cpal → 16 kHz mono (thread dedicada)
vad.rs detecção de fala por energia adaptativa
playback.rs fila de chunks + corte imediato
resample.rs reamostrador linear com estado
wav.rs encode p/ STT, decode incremental p/ TTS
pipeline/
mod.rs orquestração das três etapas sobrepostas
stt.rs Groq Whisper
llm.rs chat completions em streaming + tool-calling
segmenter.rs corta o texto em frases faláveis
tts.rs Groq/Orpheus, com edge-tts de reserva
tools/
mod.rs registro, despacho, confirmação
web.rs busca via SearXNG
files.rs arquivos com sandbox de diretório
timers.rs alarmes e temporizadores (tokio)
computer.rs abrir programas e URLs (allowlist)
docker/searxng/ instância local de busca
.github/workflows/ CI que gera .exe e .AppImage/.deb
Meta: menos de 1,5 s do fim da fala até o primeiro áudio de resposta. Isso só existe porque nada é sequencial:
| etapa | como | orçamento |
|---|---|---|
| fim da fala | VAD com hangover de 700 ms — sem segundo atalho | 0 ms |
| STT | whisper-large-v3-turbo, upload dispara no instante do fim |
~250–450 ms |
| LLM | streaming; a 1ª frase sai antes da 3ª existir | ~200–400 ms até a 1ª frase |
| TTS | 1º trecho vai com 16 caracteres; áudio toca com o 1º chunk | ~250–500 ms |
O Segmenter é o componente que define esse número: o primeiro trecho usa um
limiar baixo de propósito (first_chunk_min_chars), os seguintes usam um
limiar maior porque aí a latência já foi paga e o que importa é a prosódia.
A medição real aparece no log a cada turno (primeiro áudio em N ms) e no
canto da overlay.
O microfone não fecha durante a resposta. Se o VAD detectar voz enquanto o
agente fala, a reprodução é cortada na hora e o turno em andamento é cancelado
via CancellationToken.
Dois detalhes que fazem isso funcionar na prática:
- Contador de geração no
Playback. Interromper não é só limpar a fila: chunks de TTS já em voo continuam chegando da rede. Cada turno tem uma geração, e chunk de geração vencida é descartado em vez de tocar por cima. - Guarda de eco. O som que sai das caixas volta pelo microfone. Sem
tratamento, o agente se interrompe sozinho. Enquanto ele fala, o limiar do
VAD sobe (
ECHO_GUARD_SCALE) e há uma janela de carência de 350 ms.
Se você usa caixas de som altas em vez de fone, esses dois números são os que merecem ajuste.
winget install --id Rustlang.Rustup -eNo Windows o Rust precisa do linker da Microsoft — instale o Visual Studio Build Tools com o workload "Desenvolvimento para desktop com C++". No Linux:
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file \
libssl-dev libayatana-appindicator3-dev librsvg2-dev libasound2-dev libxdo-devlibasound2-dev (ALSA) e libxdo-dev (atalho global em X11) são os dois que
faltam na maioria dos tutoriais de Tauri e quebram o link deste projeto.
Nunca vai para o repositório. Ordem de resolução: variável de ambiente →
config.toml local.
setx GROQ_API_KEY "gsk_..." # Windows, permanente
export GROQ_API_KEY="gsk_..." # LinuxOu copie .env.example para .env (já está no .gitignore).
Se essa chave já foi colada em algum lugar em texto aberto — chat, issue, captura de tela — considere-a comprometida: gere uma nova no console da Groq e revogue a antiga antes de distribuir qualquer build.
cd docker/searxng && docker compose up -dAntes: troque secret_key em settings.yml por um valor aleatório. O
formats: [html, json] já está configurado — sem ele o SearXNG responde 403
para ?format=json e a ferramenta de busca falha.
Escolha deliberada sobre raspar o DuckDuckGo direto: raspagem quebra sem aviso quando o HTML muda e leva bloqueio por IP. O SearXNG dá uma API JSON estável e agrega vários motores.
npm installnpm run app:dev- AltGr + R (ou Ctrl+Alt+R) abre a escuta de qualquer lugar, mesmo com o app minimizado. Aperte de novo para fechar.
- Fale. O VAD sabe quando você parou — não precisa de outro atalho.
- Fale por cima para interromper.
- Esc corta a fala; Esc de novo fecha.
- Ícone na bandeja para chamar sem atalho.
Criada no primeiro uso:
- Windows:
%APPDATA%\Voica\config.toml - Linux:
~/.config/Voica/config.toml
Ajustes que valem conhecer:
| chave | padrão | o que faz |
|---|---|---|
audio.silence_ms |
700 |
quanto silêncio encerra sua fala |
audio.noise_gate |
0.006 |
piso absoluto; suba em ambiente barulhento |
audio.barge_in |
true |
permitir interromper falando |
hotkey.push_to_talk |
false |
true = segurar o atalho enquanto fala |
tts.first_chunk_min_chars |
16 |
↓ reduz latência, ↑ melhora prosódia |
tools.workspace_dir |
Documentos/Voica-Workspace | único lugar onde escreve |
tools.allowed_apps |
3 programas | lista fechada do que pode abrir |
ui.keep_open |
false |
manter a overlay com histórico |
| ferramenta | escopo |
|---|---|
web_search |
SearXNG local |
read_file / write_file / list_files |
só dentro do workspace |
delete_file |
pede confirmação |
set_reminder / list_reminders / cancel_reminder |
scheduler próprio |
open_app |
só o que está na allowlist |
open_url |
navegador padrão, hosts filtráveis |
get_datetime |
evita data alucinada |
Não existe run_command. A diferença entre "abrir um programa" e "executar
shell arbitrário" é a diferença entre uma ferramenta e um backdoor acionável
por voz — e o microfone é um canal que você não controla inteiramente
(televisão ligada, alguém na sala). Ampliar o escopo se faz acrescentando
entradas em allowed_apps, não abrindo uma porta genérica.
O sandbox de arquivos compara caminhos canonicalizados, não strings: um
symlink chamado notas apontando para C:\Windows passaria por qualquer
verificação textual. Também recusa .., caminhos absolutos, : (drive letter
no Linux, alternate data stream no NTFS) e extensões executáveis.
npm run app:build.github/workflows/build.yml roda uma matriz:
| runner | artefatos |
|---|---|
windows-latest |
.exe (NSIS) + .msi (WiX) |
ubuntu-22.04 |
.AppImage + .deb |
Dispara em workflow_dispatch, em push para main e em tag v* (que também
cria uma release em rascunho).
Ubuntu 22.04 e não 24.04 de propósito: o 24.04 linka contra uma glibc mais nova e o AppImage resultante não abre em distros mais antigas.
cd src-tauri && cargo testVale começar pelos testes: eles cobrem as partes puras (VAD, segmentador, sandbox de arquivos, acumulador de tool calls, parser de atalho, roundtrip da config) sem precisar de placa de som, chave de API ou rede.
Erros esperados na primeira passada são de assinatura de API do Tauri 2 — os pontos mais prováveis estão marcados no código. Depois:
cd src-tauri && cargo fmt --all && cargo clippy --all-targetsOs três IDs de modelo são configuráveis e mudam com frequência. Confira em console.groq.com/docs/models antes de abrir um chamado de bug:
stt.model=whisper-large-v3-turbollm.model=qwen/qwen3.6-27btts.model=canopylabs/orpheus-v1-english
O TTS padrão é um modelo em inglês, mas o agente responde em português.
canopylabs/orpheus-v1-english sintetizando texto em pt-BR vai soar errado —
sotaque e prosódia de inglês aplicados a palavras portuguesas. As saídas:
[tts]
provider = "edge_tts" # voz nativa pt-BR
fallback_voice = "pt-BR-FranciscaNeural"…ou manter a Groq e mudar llm.system_prompt para responder em inglês. O
fallback edge-tts já está implementado e usa raw-24khz-16bit-mono-pcm, então
não precisa de decodificador de MP3.
O Sec-MS-GEC do edge-tts é um alvo móvel. A Microsoft muda o esquema do
token de tempos em tempos. Se o fallback parar de funcionar, é a primeira coisa
a olhar (tts.rs, função sec_ms_gec).
Sem cancelamento de eco acústico de verdade. A guarda por limiar resolve o caso do fone e de caixas em volume moderado. Caixas altas e microfone de notebook na mesma superfície ainda podem gerar auto-interrupção.
Uso pessoal.