Files
Alpha/docs/integracion_handy.md
T

14 KiB

Alpha + Handy — Integración de Transcripción Offline

Inspiración: Handy (github.com/cjpais/Handy) — 22.4k , Tauri v2, MIT. Alpha toma de Handy su arquitectura de transcripción local con modelos Whisper/Parakeet.

Versión: 1.0 | Fecha: 2026-05-26 | Autor: Ricardo Gonzalez (Teloprax)


1. ¿Qué es Handy?

Handy es una app Tauri v2 que captura audio del micrófono, lo transcribe con modelos locales (Whisper o Parakeet) y pega el texto donde esté el cursor. Todo offline. Es la referencia perfecta para integrar transcripción en RUMBO/Alpha.

Aspecto Handy Alpha (a integrar)
Shell Tauri v2 Tauri v2
Frontend React + TS + Tailwind Vue 3 + TS + Tailwind 🔄
Transcripción transcribe-rs (Whisper + Parakeet) transcribe-rs
Captura audio cpal (micrófono) cpal (micrófono + archivos)
VAD (silencio) vad-rs (Silero) vad-rs
Atajos globales rdev + tauri-plugin-global-shortcut rdev + tauri-plugin-global-shortcut
Permisos macOS tauri-plugin-macos-permissions Ídem
Permisos Windows Win32 APIs (Windows crate) Ídem

2. Arquitectura de transcripción (basada en Handy)

┌──────────────────────────────────────────────────┐
│                  Alpha (Tauri)                    │
│                                                  │
│  ┌─────────────┐    ┌─────────────────────────┐  │
│  │ Vue 3 UI    │    │   Rust Backend          │  │
│  │ (activar    │◄──►│                         │  │
│  │  mic,       │    │  ┌───────────────────┐  │  │
│  │  modelos,   │    │  │  Audio Capture    │  │  │
│  │  transc.)   │    │  │  (cpal)           │  │  │
│  └─────────────┘    │  │  → micrófono      │  │  │
│                      │  │  → archivo .wav   │  │  │
│                      │  │  → archivo .mp3   │  │  │
│                      │  │  → video (ffmpeg  │  │  │
│                      │  │    extrae audio)  │  │  │
│                      │  └───────┬───────────┘  │  │
│                      │          │ audio raw     │  │
│                      │  ┌───────▼───────────┐  │  │
│                      │  │  VAD (vad-rs)     │  │  │
│                      │  │  → silencio OUT   │  │  │
│                      │  │  → voz → buffer   │  │  │
│                      │  └───────┬───────────┘  │  │
│                      │          │ segmentos     │  │
│                      │  ┌───────▼───────────┐  │  │
│                      │  │  transcribe-rs    │  │  │
│                      │  │  → Parakeet V3    │  │  │
│                      │  │  → Whisper Small  │  │  │
│                      │  │  → Whisper Turbo  │  │  │
│                      │  └───────┬───────────┘  │  │
│                      │          │ texto         │  │
│                      │  ┌───────▼───────────┐  │  │
│                      │  │  Post-procesado   │  │  │
│                      │  │  → pegar en app   │  │  │
│                      │  │  → guardar en DB  │  │  │
│                      │  │  → pipeline IA    │  │  │
│                      │  └───────────────────┘  │  │
│                      └─────────────────────────┘  │
└──────────────────────────────────────────────────┘

3. Dependencias Rust necesarias

Inspiradas en el Cargo.toml de Handy (v0.8.3):

[dependencies]
# Transcripción (core)
transcribe-rs = { version = "0.3", features = ["whisper-cpp", "onnx"] }

# Audio
cpal = "0.16"        # Captura de micrófono
hound = "3.5"        # Lectura/escritura de archivos .wav

# VAD (Voice Activity Detection)
vad-rs = { git = "https://github.com/cjpais/vad-rs", default-features = false }

# Atajos globales
rdev = { git = "https://github.com/rustdesk-org/rdev" }
tauri-plugin-global-shortcut = "2.3"

# Permisos
tauri-plugin-macos-permissions = "2.3"

# Resampling (si se necesita cambiar sample rate)
rubato = "0.16"

# Audio playback (feedback sonoro)
rodio = "0.16"

# Utilidades
anyhow = "1.0"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# ─── Plataforma-específicas ───

[target.'cfg(target_os = "macos")'.dependencies]
transcribe-rs = { version = "0.3", features = ["whisper-metal"] }

[target.'cfg(target_os = "windows")'.dependencies]
transcribe-rs = { version = "0.3", features = ["whisper-vulkan", "ort-directml"] }

[target.'cfg(target_os = "linux")'.dependencies]
transcribe-rs = { version = "0.3", features = ["whisper-vulkan"] }

4. Modelos de transcripción

4.1 Parakeet V3 (RECOMENDADO)

Característica Detalle
Tamaño ~478 MB (comprimido)
Velocidad ~5x tiempo real en CPU (i5)
GPU No requiere — CPU-optimizado
Idioma Detección automática (sin selección manual)
Precisión Excelente en español e inglés
Descarga https://blob.handy.computer/parakeet-v3-int8.tar.gz
Backend ONNX Runtime (vía transcribe-rs feature onnx)

Por qué Parakeet V3: No requiere GPU, funciona en cualquier CPU moderna (Skylake+), velocidad adecuada para transcripción interactiva, y detección automática de idioma (esencial para equipos bilingües español/inglés).

4.2 Whisper (alternativa)

Modelo Tamaño Velocidad GPU
Small 487 MB Rápido Opcional
Medium 492 MB Medio Recomendada
Turbo 1.6 GB Rápido Recomendada
Large 1.1 GB Lento Necesaria

Whisper es más preciso pero más pesado y requiere GPU para velocidad aceptable. Parakeet V3 es mejor opción para Alpha por su eficiencia CPU.


5. ¿Parakeet V3 para video/audio pregrabado?

, pero con un paso intermedio: extraer el audio del video.

Flujo para video

video.mp4 → ffmpeg (extraer pista de audio) → audio.wav → transcribe-rs → texto

Flujo para audio pregrabado

grabacion.mp3/.ogg/.flac/.m4a → decodificar a PCM/WAV → transcribe-rs → texto

Implementación en Rust

// Opción A: Usar ffmpeg CLI (sidecar)
use std::process::Command;
Command::new("ffmpeg")
    .args(["-i", "video.mp4", "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "audio.wav"])
    .output()?;

// Opción B: Usar crate ffmpeg-sidecar (más portátil)
// Opción C: Usar crate symphonia para decodificar audio sin ffmpeg

Formatos soportados

Fuente Método Librería Rust
Micrófono (vivo) cpal → buffer → transcribe-rs cpal
.wav hound → samples → transcribe-rs hound
.mp3, .ogg, .flac, .m4a symphonia → decode → transcribe-rs symphonia
Video (.mp4, .mov, .mkv) ffmpeg → extract audio → transcribe-rs ffmpeg-sidecar

6. Permisos del sistema

6.1 macOS

Permiso Propósito Cómo solicitarlo
Microphone Capturar audio del micrófono Info.plist: NSMicrophoneUsageDescription
Accessibility Pegar texto en cualquier app System Preferences → Privacy → Accessibility
Screen Recording (futuro) Capturar audio del sistema Info.plist: NSScreenCaptureUsageDescription

Código Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>Alpha necesita acceso al micrófono para transcribir tus reuniones.</string>

Detección de permiso (Rust):

use tauri_plugin_macos_permissions;

// Verificar si el permiso está concedido
let authorized = tauri_plugin_macos_permissions::check_accessibility();

// Solicitar permiso
tauri_plugin_macos_permissions::request_accessibility();

6.2 Windows

Permiso Propósito Cómo solicitarlo
Microphone Capturar audio Windows Settings → Privacy → Microphone
Global shortcuts Atajos de teclado Automático con rdev

Windows solicita el permiso automáticamente la primera vez que se usa el micrófono.

6.3 Linux

Requisito Propósito
PulseAudio / ALSA Captura de audio
Grupo input Atajos globales (algunos WMs)

7. Flujo de transcripción paso a paso

7.1 Micrófono (tiempo real)

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use transcribe_rs::Transcriber;

// 1. Obtener dispositivo de audio por defecto
let host = cpal::default_host();
let device = host.default_input_device().unwrap();
let config = device.default_input_config().unwrap();

// 2. Configurar stream de audio
let (tx, rx) = std::sync::mpsc::channel();
let stream = device.build_input_stream(&config, move |data, _| {
    tx.send(data.to_vec()).unwrap();
}, |err| eprintln!("{}", err), None).unwrap();
stream.play().unwrap();

// 3. Buffer de audio + VAD
let mut buffer: Vec<f32> = Vec::new();
let mut vad = vad_rs::Vad::new(config.sample_rate().0);

// 4. Acumular voz, ignorar silencio
while let Ok(samples) = rx.recv_timeout(Duration::from_millis(100)) {
    for &sample in &samples {
        if vad.is_voice(sample) {
            buffer.push(sample);
        }
    }
    if buffer.len() > config.sample_rate().0 as usize * 3 {
        break; // 3 segundos de voz → transcribir
    }
}

// 5. Transcribir con Parakeet V3
let transcriber = Transcriber::new("parakeet-tdt-0.6b-v3-int8")?;
let text = transcriber.transcribe(&buffer, config.sample_rate().0)?;
println!("{}", text);

7.2 Archivo de audio/video

// 1. Si es video, extraer audio con ffmpeg
// 2. Leer archivo .wav
use hound::WavReader;
let mut reader = WavReader::open("audio.wav")?;
let samples: Vec<f32> = reader.samples::<i16>()
    .map(|s| s.unwrap() as f32 / i16::MAX as f32)
    .collect();

// 3. Transcribir
let transcriber = Transcriber::new("parakeet-tdt-0.6b-v3-int8")?;
let text = transcriber.transcribe(&samples, reader.spec().sample_rate)?;

8. Descarga y gestión de modelos

Inspirado en Handy, los modelos se almacenan en:

SO Ruta
macOS ~/Library/Application Support/com.teloprax.alpha/models/
Windows %APPDATA%\com.teloprax.alpha\models\
Linux ~/.config/com.teloprax.alpha/models/

Estructura esperada

models/
├── parakeet-tdt-0.6b-v3-int8/   ← Parakeet V3 (recomendado)
│   ├── model.onnx
│   ├── config.json
│   └── tokenizer.json
├── ggml-small.bin               ← Whisper Small (alternativo)
└── ggml-large-v3-turbo.bin      ← Whisper Turbo (alternativo)

Descarga programática

use reqwest;
use flate2::read::GzDecoder;
use tar::Archive;

async fn download_model(url: &str, dest: &Path) -> Result<()> {
    let response = reqwest::get(url).await?;
    let bytes = response.bytes().await?;

    // Extraer si es .tar.gz (Parakeet), copiar si es .bin (Whisper)
    if url.ends_with(".tar.gz") {
        let gz = GzDecoder::new(&bytes[..]);
        let mut archive = Archive::new(gz);
        archive.unpack(dest)?;
    } else {
        std::fs::write(dest.join(url.split('/').last().unwrap()), &bytes)?;
    }
    Ok(())
}

9. Diferencia clave Alpha vs Handy

Handy es una app de propósito general (transcribir y pegar). Alpha lo integra en el flujo de gestión de proyectos:

Capacidad Handy Alpha
Transcribir micrófono (para reuniones)
Pegar texto en cualquier app (no necesario)
Transcribir archivos de audio (.mp3, .wav, .ogg)
Extraer audio de video (vía ffmpeg)
Pipeline IA post-transcripción (→ análisis → HUs → KAPPA)
Guardar transcripción en proyecto (Turso + contexto de proyecto)
Memoria de proyecto (contexto_transcripciones.md)
Integración con KAPPA (crear HUs automáticamente)

10. Roadmap de integración

Fase Alcance Prioridad
T1 Agregar dependencias Rust (transcribe-rs, cpal, vad-rs) 🔴 Alta
T2 Captura de micrófono + transcripción Parakeet V3 🔴 Alta
T3 Descarga de modelos desde UI 🟡 Media
T4 Transcripción de archivos (.wav, .mp3) 🟡 Media
T5 Transcripción de video (extracción audio) 🟢 Baja
T6 Pipeline completo: audio → transcripción → IA → HUs 🔴 Alta
T7 Permisos macOS/Windows UI 🟡 Media

11. Referencias