Files
transcript/README.md
T
evandro 2c573a49ea
Desktop Build / build (linux-bundles, ubuntu-22.04) (push) Canceled after 0s
Desktop Build / build (windows-bundles, windows-latest) (push) Canceled after 0s
CI / quality (push) Canceled after 0s
docs(readme): padroniza README (hero, badges, quickstart, arquitetura)
2026-10-24 04:13:06 +00:00

11 KiB
Raw Blame History

Transcript Desktop

Ditado offline para Windows e Linux: grave com um atalho global, revise o texto e cole no app em foco.

Quickstart · Arquitetura · Documentação · Issues

Stack Status Licença


📌 O problema

Ditar texto em qualquer aplicativo do desktop exige gravar, transcrever e colar o resultado por conta própria, e boa parte das opções depende de serviço online. O Transcript fica residente na bandeja, grava por atalho global, transcreve localmente e deixa o texto editável antes de inseri-lo no aplicativo em foco.

✨ O que faz

  • Alterna a gravação com o atalho global Ctrl+Shift+Space (evento transcript://toggle-recording para o frontend).
  • Captura o microfone em WAV (cpal + hound) na pasta de dados do app.
  • Transcreve localmente chamando o sidecar sherpa-onnx-offline com o modelo Parakeet TDT 0.6B int8 e guarda um diagnóstico de cada tentativa (etapa, exit code, final de stdout/stderr).
  • Inclui adaptadores para o sidecar CrispASR (modelo GGUF legado) e para APIs compatíveis com OpenAI.
  • Mostra o texto num editor, com os botões Copiar e Inserir, para revisar antes de colar.
  • Cola no app em foco: copia para o clipboard e aciona Ctrl+V (PowerShell no Windows, xdotool no X11).
  • Mantém ícone na bandeja com o menu Mostrar app, Iniciar gravação e Sair; o clique esquerdo mostra a janela.
  • Lista o catálogo de modelos com status e baixa o modelo padrão.

🚀 Quickstart

Ferramenta Versão
Node.js + npm 22 (usada nos workflows; não há .nvmrc nem engines)
Rust (cargo) stable, edition 2021 (workflows usam dtolnay/rust-toolchain@stable)
Tauri CLI ^2.9.0 (devDependency)
Linux (build) libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev
Linux X11 (colar) xdotool, opcional
tar com bzip2 usado por scripts/setup-fixtures.mjs
npm ci
node scripts/setup-sidecar.mjs   # baixa sherpa-onnx-offline para src-tauri/binaries/
npm run tauri:dev                # app em desenvolvimento (Vite em 127.0.0.1:1420)
npm run tauri:build              # bundles em src-tauri/target/release/bundle/

O modelo não vai no instalador: o app o baixa para a pasta de dados (%LOCALAPPDATA%\transcript\models no Windows, ~/.local/share/transcript/models no Linux) pelo comando Tauri download_default_model_command. As gravações ficam em recordings/ na mesma pasta.

Não há .env.example e nenhuma variável é obrigatória. As opcionais:

Variável Obrigatória Descrição
SHERPA_ONNX_SIDECAR Não Caminho do sidecar; tem prioridade sobre src-tauri/binaries/ e sobre o PATH.
SHERPA_ONNX_VERSION Não Versão do release sherpa-onnx baixada por setup-sidecar.mjs (padrão v1.12.23).
SHERPA_ONNX_TRIPLE Não Target triple do sidecar (padrão: detectado via rustc -vV).

🧱 Arquitetura

flowchart LR
    HK["Hotkey global Ctrl+Shift+Space"] --> UI["Frontend TypeScript"]
    UI --> CMD["Comandos Tauri em Rust"]
    CMD --> REC["Gravação WAV com cpal e hound"]
    REC --> ASR["Sidecar sherpa-onnx-offline"]
    ASR --> UI
    UI -->|Inserir| INS["Clipboard e Ctrl+V no app em foco"]

O hotkey emite um evento que o frontend escuta para iniciar ou parar a gravação. Ao parar, stop_recording_and_transcribe fecha o WAV, roda o sidecar e devolve o texto num snapshot para o editor. Só depois da revisão o botão Inserir chama request_insert_text, que copia o texto e tenta colar. As threads do sidecar são limitadas a 1–4.

Caminhos de transcrição

Caminho Arquivo Estado no código
Sidecar sherpa-onnx (local) transcription/sherpa_onnx.rs É o único chamado por stop_recording_and_transcribe. Procura o binário em SHERPA_ONNX_SIDECAR, depois em src-tauri/binaries/sherpa-onnx-offline-<triple>, depois no PATH.
Sidecar CrispASR (local) transcription/crispasr.rs Adaptador que executa <sidecar> -m <modelo.gguf> -f <audio.wav> -t <threads>. Hoje nenhum comando o instancia. O spec original recomendava compilar o parakeet-main do CrispASR (branch parakeet).
API OpenAI-compatible transcription/openai.rs Adaptador de envio do WAV. settings guarda mode (localFirst ou apiOnly, padrão apiOnly) e o endpoint padrão http://localhost:8022/v1/audio/transcriptions.

Modelos

ID Formato Tamanho no catálogo Idiomas Uso
sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8 tarball .tar.bz2 do release asr-models do k2-fsa/sherpa-onnx (encoder, decoder, joiner int8 + tokens.txt) ~640 MB 25 idiomas europeus, incluindo pt Padrão; recomendado para PC simples
parakeet-tdt-0.6b-v3-q8_0 GGUF Q8_0, repositório cstr/parakeet-tdt-0.6b-v3-GGUF 745121600 bytes pt, en Legado, para o adaptador CrispASR

Compatibilidade por sistema

Sistema Inserção no app em foco Fallback
Windows Clipboard + Ctrl+V via PowerShell SendKeys Se o Ctrl+V falhar, o texto fica copiado e a mensagem avisa.
Linux X11 Clipboard + xdotool key --clearmodifiers ctrl+v Sem xdotool, o texto fica copiado para colar manualmente.
Linux Wayland Só copia (suporte parcial) Sem colagem automática: cole com Ctrl+V.
Linux sem sessão detectada Só copia Cole manualmente no aplicativo desejado.

A sessão é detectada por XDG_SESSION_TYPE, WAYLAND_DISPLAY e DISPLAY. O método de inserção configurável é clipboardPaste (padrão), keyTyping ou manualCopy.

🗂️ Estrutura

src/                   Frontend TypeScript (app.ts, main.ts, styles.css, app.test.ts)
src-tauri/src/
  audio/               Captura do microfone em WAV
  transcription/       sherpa_onnx.rs, crispasr.rs, openai.rs
  injection/           Clipboard, paste automático e fallback manual
  models/              Catálogo e download de modelos
  settings/            Configuração padrão
  tray.rs shortcuts.rs ui_commands.rs app_state.rs diagnostics.rs error.rs
src-tauri/binaries/    Sidecar sherpa-onnx-offline e DLLs onnxruntime (Windows)
src-tauri/tests/fixtures/  Modelo ONNX grande para teste real (o clone passa de 1 GB)
scripts/               setup-sidecar.mjs, setup-fixtures.mjs
.github/workflows/     ci.yml, build.yml, release.yml

🧪 Scripts

Comando O que faz
npm run dev Vite em 127.0.0.1:1420, só o frontend.
npm run build tsc --noEmit e vite build.
npm test vitest (ambiente jsdom); em modo não interativo use npm test -- --run, como o CI.
npm run typecheck tsc --noEmit.
npm run tauri:dev tauri dev.
npm run tauri:build tauri build.
cargo test --manifest-path src-tauri/Cargo.toml Testes Rust.
node scripts/setup-sidecar.mjs Baixa o release *-shared do sherpa-onnx e extrai sherpa-onnx-offline para src-tauri/binaries/. Plataformas: Linux x64/arm64, macOS, Windows x64.
node scripts/setup-fixtures.mjs Baixa parakeet-sample.wav para src-tauri/tests/fixtures/.

Workflows (.github/workflows/)

Arquivo Gatilho O que faz
ci.yml PR e push em main Em ubuntu-22.04 com Node 22: npm ci, typecheck, npm run test -- --run, build e cargo test. Depois prepara sidecar, fixture e modelo, e roda o teste real de transcrição com --ignored.
build.yml Push em main e manual Matriz windows-latest e ubuntu-22.04: npm run tauri:build e upload de src-tauri/target/release/bundle/**/* (windows-bundles, linux-bundles).
release.yml Tag v*.*.* e manual (input tag) tauri-apps/tauri-action@v0 nas mesmas duas plataformas; publica a GitHub Release Transcript <tag>, sem rascunho e sem pré-release.

📚 Documentação

🛣️ Limitações e próximos passos

  • whisper.cpp: só aparece no spec do redesign, e bundle.resources aponta para resources/whisper/, pasta que não existe no repositório. Não há código nem modelo Whisper no catálogo; whisper-1 é só um exemplo de nome de modelo na API.
  • O app ainda não escolhe o caminho de transcrição por mode: stop_recording_and_transcribe sempre usa o sherpa-onnx.
  • tauri.conf.json não declara bundle.externalBin, embora o spec preveja o sidecar empacotado no instalador.
  • Divergências vistas na leitura do código, que não foi compilado: o frontend invoca stop_recording, transcribe_pending, minimize_window, close_window, save_openai_api_key e clear_openai_api_key, ausentes do invoke_handler de lib.rs; openai.rs importa crate::secrets, que lib.rs não declara; ui_commands.rs lê settings.transcription.model_id e settings usa shortcuts::DEFAULT_HOTKEY, e nenhum dos dois existe.
  • O hotkey Ctrl+Shift+Space é fixo em shortcuts.rs, e o item Iniciar gravação da bandeja só mostra a janela.
  • O catálogo aponta para o modelo v2-int8, mas o ci.yml baixa o v3-int8 (~670 MB) e cita o teste real_transcription_returns_known_text, que não está em sherpa_onnx.rs.
  • As versões divergem: 0.1.0 em package.json e Cargo.toml, 0.3.1 em tauri.conf.json.

🤝 Contribuindo

Mantenha módulos pequenos, com responsabilidades claras. Antes de abrir o PR, estes comandos devem passar:

npm run typecheck && npm test -- --run && cargo test --manifest-path src-tauri/Cargo.toml

📄 Licença

Sem licença definida — todos os direitos reservados.