Skip to content

Repository files navigation

Voica

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 ────────┘

Estado atual

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.


Arquitetura

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

Latência

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.

Barge-in

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.


Instalação

1. Toolchain

winget install --id Rustlang.Rustup -e

No 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-dev

libasound2-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.

2. Chave da Groq

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_..."          # Linux

Ou 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.

3. SearXNG

cd docker/searxng && docker compose up -d

Antes: 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.

4. Dependências do frontend

npm install

Uso

npm 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.

Configuração

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

Ferramentas

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.


Build

Local

npm run app:build

CI — os dois sistemas, do mesmo commit

.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.


Primeira compilação

cd src-tauri && cargo test

Vale 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-targets

Pontos a verificar antes de considerar pronto

Os 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-turbo
  • llm.model = qwen/qwen3.6-27b
  • tts.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.


Licença

Uso pessoal.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages