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

172 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div align="center">
<h1>Transcript Desktop</h1>
<p><strong>Ditado offline para Windows e Linux: grave com um atalho global, revise o texto e cole no app em foco.</strong></p>
<a href="#-quickstart">Quickstart</a> ·
<a href="#-arquitetura">Arquitetura</a> ·
<a href="#-documentação">Documentação</a> ·
<a href="https://git.evandro.dev.br/evandro/transcript/issues">Issues</a>
![Stack](https://img.shields.io/badge/Stack-Tauri%202%20%2B%20Rust%20%2B%20TypeScript-24C8DB?logo=tauri&logoColor=white)
![Status](https://img.shields.io/badge/status-MVP-yellow)
![Licença](https://img.shields.io/badge/licen%C3%A7a-n%C3%A3o%20definida-lightgrey)
</div>
<hr />
## 📌 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` |
```bash
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
```mermaid
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
```text
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
- [Design do app](docs/superpowers/specs/2026-06-10-transcription-app-design.md)
- [sherpa-onnx como sidecar](docs/superpowers/specs/2026-06-11-sherpa-onnx-sidecar.md)
- [Redesign do frontend](docs/superpowers/specs/2026-08-08-frontend-redesign-design.md)
- Planos: [MVP](docs/superpowers/plans/2026-06-10-transcription-app-mvp.md), [integrações nativas](docs/superpowers/plans/2026-06-10-transcription-app-native-integrations.md), [redesign](docs/superpowers/plans/2026-08-08-frontend-redesign.md)
- Tauri: [commands](https://v2.tauri.app/develop/calling-rust), [system tray](https://v2.tauri.app/learn/system-tray), [sidecars](https://v2.tauri.app/develop/sidecar), [pipelines GitHub](https://v2.tauri.app/distribute/pipelines/github), [pré-requisitos Linux](https://v2.tauri.app/start/prerequisites)
- Modelos: [NVIDIA Parakeet v3](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3/blob/main/README.md), [cstr GGUF](https://huggingface.co/cstr/parakeet-tdt-0.6b-v3-GGUF)
## 🛣️ 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:
```bash
npm run typecheck && npm test -- --run && cargo test --manifest-path src-tauri/Cargo.toml
```
## 📄 Licença
Sem licença definida — todos os direitos reservados.