diff options
| author | elvis <elvis@claros.ar> | 2026-09-26 20:20:19 -0300 |
|---|---|---|
| committer | elvis <elvis@claros.ar> | 2026-09-26 20:20:19 -0300 |
| commit | 8518a63f55153e7f45fd49ad6caff5555f4e374f (patch) | |
| tree | 636684eea3fa6f35d78282ab95eb687ef49154af /crates/asist-core/src | |
| parent | 69de76dc9cbedc6092d1e5ce84094a8030de1470 (diff) | |
| download | asist-p-main.tar.gz asist-p-main.zip | |
Translate code, comments, logs and terminal UI to English; add English README; rename scriptsHEADmain
Diffstat (limited to 'crates/asist-core/src')
| -rw-r--r-- | crates/asist-core/src/config.rs | 271 | ||||
| -rw-r--r-- | crates/asist-core/src/error.rs | 30 | ||||
| -rw-r--r-- | crates/asist-core/src/event.rs | 52 | ||||
| -rw-r--r-- | crates/asist-core/src/http.rs | 66 | ||||
| -rw-r--r-- | crates/asist-core/src/lib.rs | 10 | ||||
| -rw-r--r-- | crates/asist-core/src/proc.rs | 80 | ||||
| -rw-r--r-- | crates/asist-core/src/telemetry.rs | 88 | ||||
| -rw-r--r-- | crates/asist-core/src/text.rs | 124 | ||||
| -rw-r--r-- | crates/asist-core/src/tools.rs | 158 |
9 files changed, 440 insertions, 439 deletions
diff --git a/crates/asist-core/src/config.rs b/crates/asist-core/src/config.rs index c2a9d55..0a8cc04 100644 --- a/crates/asist-core/src/config.rs +++ b/crates/asist-core/src/config.rs @@ -1,5 +1,5 @@ -//! Configuración del asistente: un TOML con valores por defecto que ya -//! incorporan lo aprendido midiendo el pipeline (ver `docs/RENDIMIENTO.md`). +//! Assistant configuration: a TOML file whose defaults already include what +//! was learned by measuring the pipeline (see `docs/RENDIMIENTO.md`). use std::path::{Path, PathBuf}; @@ -26,32 +26,31 @@ pub struct Config { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct General { - /// Idioma de la conversación; se propaga a ASR y TTS. + /// Conversation language; propagated to ASR and TTS. pub language: String, - /// Instrucción de sistema. Pide frases cortas y sin markdown a propósito: - /// el TTS lee literalmente los asteriscos y las viñetas. + /// System prompt. It asks for short sentences without markdown on purpose: + /// the TTS reads asterisks and bullets literally. pub system_prompt: String, - /// Instrucción de sistema de la pasada en que el modelo decide si usar - /// una herramienta. + /// System prompt for the pass where the model decides whether to use a + /// tool. /// - /// Va aparte, y **sustituye** a `system_prompt` en esa pasada, por una - /// razón medida y no por gusto: con Qwen3.5-2B, añadir cualquier - /// indicación de estilo a esta guía —en cualquier posición, incluso dos - /// palabras— hace que el modelo deje de llamar a las herramientas y se - /// invente el dato. Medido sobre 8 intentos: la guía sola acierta 8/8; - /// con «Responde breve.» detrás, 1/8; con la persona de asistente de voz, - /// 0/8. Ver docs/RENDIMIENTO.md. + /// It is separate, and **replaces** `system_prompt` in that pass, for a + /// measured reason rather than taste: with Qwen3.5-2B, adding any style + /// instruction to this guide (in any position, even two words) makes the + /// model stop calling tools and make the data up. Measured over 8 tries: + /// the guide alone gets 8/8; with «Responde breve.» after it, 1/8; with the + /// voice-assistant persona, 0/8. See docs/RENDIMIENTO.md. pub tools_prompt: String, - /// Se añade a `system_prompt` para redactar la respuesta cuando una - /// herramienta ya ha devuelto su resultado. + /// Appended to `system_prompt` to write the answer once a tool has + /// returned its result. /// - /// Aquí sí se puede añadir estilo sin miedo —la llamada ya ocurrió—, y - /// hace falta: sin esta orden el modelo anuncia lo que acaba de hacer en - /// vez de contar lo que averiguó. + /// Here style can be added safely (the call already happened), and it is + /// needed: without this instruction the model announces what it just did + /// instead of telling what it found out. pub tool_result_prompt: String, - /// Turnos de historial que se envían al modelo (0 = sin memoria). + /// History turns sent to the model (0 = no memory). pub history_turns: usize, - /// Imprime un resumen de latencias por turno al terminar cada respuesta. + /// Prints a per-turn latency summary at the end of each answer. pub report_latency: bool, } @@ -89,14 +88,14 @@ impl Default for General { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct AudioConfig { - /// Nombre (o subcadena) del dispositivo de entrada; vacío = el de por defecto. + /// Name (or substring) of the input device; empty = the default one. pub input_device: String, - /// Ídem para la salida. + /// Same for the output. pub output_device: String, - /// Segundos de audio que la reproducción mantiene en cola antes de - /// arrancar. Amortigua los baches del TTS sin añadir latencia perceptible. + /// Seconds of audio playback keeps queued before starting. It absorbs + /// TTS hiccups without adding noticeable latency. pub playback_prebuffer: f32, - /// Ganancia aplicada a la reproducción. + /// Gain applied to playback. pub output_gain: f32, } @@ -114,28 +113,28 @@ impl Default for AudioConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct VadConfig { - /// Ventana de audio por decisión del detector. + /// Audio window per detector decision. pub frame_seconds: f32, - /// Audio conservado antes del inicio de voz para no cortar la primera sílaba. + /// Audio kept before speech onset so the first syllable is not cut. pub preroll_seconds: f32, - /// Silencio que cierra una intervención. + /// Silence that ends an utterance. pub silence_hold: f32, - /// Intervención más corta que merezca una transcripción final. + /// Shortest utterance worth a final transcription. pub min_utterance: f32, - /// Corte forzado, que acota el coste de la decodificación final. + /// Forced cut, which bounds the cost of the final decode. pub max_utterance: f32, - /// Múltiplo del suelo de ruido a partir del cual se considera voz. + /// Multiple of the noise floor above which audio counts as speech. pub threshold_factor: f32, pub min_threshold: f32, pub max_threshold: f32, - /// Permite hablar encima del asistente para cortarlo. + /// Lets the user talk over the assistant to interrupt it. /// - /// Desactivado por defecto: con altavoces abiertos el micrófono se oye a sí - /// mismo y el asistente se interrumpe solo. Actívalo con auriculares o con - /// cancelación de eco del sistema. + /// Off by default: with open speakers the microphone hears itself and the + /// assistant interrupts itself. Turn it on with headphones or with system + /// echo cancellation. pub barge_in: bool, - /// Con barge-in activo, cuánto más fuerte que el umbral normal debe sonar - /// la voz para cortar. Sube el listón frente al eco del altavoz. + /// With barge-in on, how much louder than the normal threshold the voice + /// must be to interrupt. Raises the bar against speaker echo. pub barge_in_factor: f32, } @@ -159,23 +158,23 @@ impl Default for VadConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct AsrConfig { - /// Carpeta del modelo Canary en ONNX. + /// Directory of the Canary ONNX model. pub model_dir: PathBuf, pub source_lang: String, pub target_lang: String, - /// Ventana deslizante de las transcripciones provisionales. + /// Sliding window for the partial transcriptions. pub window: f32, - /// Avance de la ventana entre decodificaciones. + /// Window step between decodes. pub step: f32, - /// Ventanas que deben coincidir para dar una palabra por estable. + /// Windows that must agree for a word to be considered stable. pub stability: usize, - /// Emitir transcripciones provisionales. Cuestan CPU y sólo sirven para - /// verlas en pantalla: el turno se dispara con la final. + /// Emit partial transcriptions. They cost CPU and are only there to be + /// shown on screen: the turn is triggered by the final one. pub partials: bool, - /// Carga una segunda instancia del modelo para que las decodificaciones - /// finales no bloqueen a las provisionales. Duplica la memoria. + /// Loads a second model instance so final decodes do not block the + /// partial ones. Doubles the memory. pub dedicated_final_model: bool, - /// Proveedor de ejecución de ONNX Runtime: `cpu`, `cuda`, ... + /// ONNX Runtime execution provider: `cpu`, `cuda`, ... pub execution_provider: String, pub inter_threads: usize, pub intra_threads: usize, @@ -192,8 +191,8 @@ impl Default for AsrConfig { stability: 2, partials: true, dedicated_final_model: false, - // CPU a propósito: la GPU de 4 GB está ocupada por el hablante del - // TTS, y disputársela sale más caro que decodificar en CPU. + // CPU on purpose: the 4 GB GPU is taken by the TTS talker, and fighting + // over it costs more than decoding on the CPU. execution_provider: "cpu".into(), inter_threads: 2, intra_threads: 4, @@ -213,7 +212,7 @@ pub struct LlmConfig { pub min_p: f32, pub repeat_penalty: f32, pub max_tokens: u32, - /// Vueltas máximas del bucle de herramientas antes de rendirse. + /// Maximum rounds of the tool loop before giving up. pub max_tool_rounds: usize, pub request_timeout_secs: u64, } @@ -229,8 +228,8 @@ impl Default for LlmConfig { top_k: 40, min_p: 0.1, repeat_penalty: 1.1, - // Una respuesta hablada larga cansa; el recorte también acota el - // coste de la síntesis, que es la etapa lenta. + // A long spoken answer is tiring; the cap also bounds the cost of + // synthesis, which is the slow stage. max_tokens: 300, max_tool_rounds: 4, request_timeout_secs: 120, @@ -243,9 +242,9 @@ impl Default for LlmConfig { pub struct TtsConfig { pub host: String, pub port: u16, - /// Voz registrada en el servidor, o una del modelo. + /// Voice registered on the server, or one of the model's. pub voice: String, - /// Voz clonada que se registra al arrancar, si se define. + /// Cloned voice registered at startup, if defined. pub reference: Option<ReferenceVoice>, pub language: String, pub temperature: f32, @@ -253,9 +252,8 @@ pub struct TtsConfig { pub top_p: f32, pub repetition_penalty: f32, pub max_new_tokens: u32, - /// Sintetiza una frase corta al arrancar. La primera petición paga la - /// construcción de los grafos: ~3,5 s que conviene no gastar en el - /// primer turno real. + /// Synthesizes a short sentence at startup. The first request pays for + /// building the graphs: ~3.5 s better not spent on the first real turn. pub warmup: bool, pub request_timeout_secs: u64, } @@ -279,52 +277,51 @@ impl Default for TtsConfig { } } -/// Latentes de una voz clonada, tal y como los produce `qwen-codec --talker`. +/// Latents of a cloned voice, as produced by `qwen-codec --talker`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ReferenceVoice { - /// Nombre con el que se registra en el servidor. + /// Name it is registered under on the server. pub name: String, - /// Embedding del hablante (`.spk`). + /// Speaker embedding (`.spk`). pub speaker: PathBuf, - /// Códigos de referencia (`.rvq`), que activan el clonado ICL. + /// Reference codes (`.rvq`), which enable ICL cloning. pub codes: PathBuf, - /// Transcripción de la referencia (`.txt`). + /// Transcript of the reference (`.txt`). pub transcript: PathBuf, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct ToolsConfig { - /// Deja que el modelo llame a herramientas. + /// Lets the model call tools. pub enabled: bool, - /// Usa `general.tools_prompt` en solitario para la pasada en que el - /// modelo decide si llamar a una herramienta, y `general.system_prompt` - /// sólo para redactar la respuesta hablada. + /// Uses `general.tools_prompt` alone for the pass where the model decides + /// whether to call a tool, and `general.system_prompt` only to write the + /// spoken answer. /// - /// Es lo único que hace que las herramientas funcionen de verdad con este - /// modelo (ver `tools_prompt`), a cambio de que las respuestas que no - /// usan herramienta pierdan la guía de estilo. Se compensa en parte - /// porque el limpiador de texto quita el markdown antes de hablar. - /// Ponlo a `false` para priorizar el estilo sobre las herramientas. + /// It is the only thing that makes tools really work with this model (see + /// `tools_prompt`), at the cost of answers that use no tool losing the + /// style guide. That is partly offset by the text cleaner removing + /// markdown before speaking. Set it to `false` to favour style over tools. pub dedicated_prompt: bool, - /// Pronuncia una frase corta al empezar una herramienta lenta, para que la - /// espera no se confunda con un cuelgue. + /// Says a short sentence when a slow tool starts, so the wait is not + /// mistaken for a hang. pub spoken_ack: bool, - /// Habilita la herramienta de ejecución de órdenes del sistema. + /// Enables the tool that runs system commands. /// - /// Apagada por defecto a conciencia: darle una shell a un modelo que - /// obedece a lo que oye por el micrófono es un cambio de postura de - /// seguridad, no una opción de comodidad. + /// Off by default on purpose: giving a shell to a model that obeys what + /// it hears through the microphone is a change of security posture, not a + /// convenience option. pub shell: bool, - /// Órdenes admitidas, comparadas contra el ejecutable (argv[0]). - /// Una lista vacía deniega todo aunque `shell` esté activo. + /// Allowed commands, matched against the executable (argv[0]). + /// An empty list denies everything even if `shell` is on. pub shell_allowlist: Vec<String>, - /// Segundos que puede durar una orden antes de que se la mate. + /// Seconds a command may run before it is killed. pub shell_timeout_secs: u64, - /// Registra la orden pero no la ejecuta. Útil para estrenar la lista blanca. + /// Logs the command but does not run it. Useful to break in the allowlist. pub shell_dry_run: bool, - /// Directorio de trabajo de las órdenes; vacío = el del proceso. + /// Working directory of the commands; empty = the process one. pub shell_working_dir: String, } @@ -349,29 +346,28 @@ impl Default for ToolsConfig { } } -/// Búsqueda en internet. +/// Web search. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct SearchConfig { pub enabled: bool, - /// `tavily` (necesita clave), `ddgs` (sin clave) o `searxng` (necesita - /// una instancia propia). + /// `tavily` (needs a key), `ddgs` (no key) or `searxng` (needs your own + /// instance). pub backend: String, - /// Variable de entorno de la que se lee la clave. + /// Environment variable the key is read from. /// - /// La clave se lee del entorno y no del fichero a propósito: la - /// configuración se versiona y se comparte, y un secreto ahí dentro acaba - /// en el historial de git. + /// The key comes from the environment and not from the file on purpose: + /// the configuration is versioned and shared, and a secret in it ends up in + /// the git history. pub api_key_env: String, - /// URL base de la instancia de SearXNG. + /// Base URL of the SearXNG instance. pub base_url: String, - /// Programa que ejecuta el backend `ddgs`, con sus argumentos. + /// Program that runs the `ddgs` backend, with its arguments. /// - /// `{consulta}` y `{max}` se sustituyen antes de ejecutar. Tiene que - /// escribir JSON por la salida estándar; se admiten tanto la lista suelta - /// que devuelve ddgs como la forma `{answer, results}`. Cualquier otro - /// programa que respete eso sirve igual, incluido un puente a un servidor - /// MCP. + /// `{query}` and `{max}` are substituted before running. It must write + /// JSON to standard output; both the bare list ddgs returns and the + /// `{answer, results}` shape are accepted. Any other program that honours + /// that works too, including a bridge to an MCP server. pub command: Vec<String>, pub max_results: usize, pub timeout_secs: u64, @@ -385,35 +381,35 @@ impl Default for SearchConfig { api_key_env: "TAVILY_API_KEY".into(), base_url: String::new(), command: vec![ - "scripts/buscar-ddgs.sh".into(), - "{consulta}".into(), + "scripts/search-ddgs.sh".into(), + "{query}".into(), "{max}".into(), ], - // Cinco resultados llenan bien el contexto sin ahogar a un modelo - // de 2B, que con más se pierde entre fuentes. + // Five results fill the context well without drowning a 2B model, which + // gets lost between sources with more. max_results: 5, timeout_secs: 20, } } } -/// Mirar por la cámara. +/// Looking through the camera. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct CameraConfig { - /// Si se apaga, la herramienta no llega a registrarse y el modelo ni - /// sabe que existe. También se apaga sola si no hay dispositivo. + /// If off, the tool is never registered and the model does not even know + /// it exists. It also turns itself off if there is no device. pub enabled: bool, pub device: PathBuf, - /// Resolución de captura. Manda mucho en la latencia: medido, el modelo - /// tarda 1,3 s a 320x240, 2,9 s a 640x480 y 7,8 s a 1280x720. + /// Capture resolution. It weighs heavily on latency: measured, the model + /// takes 1.3 s at 320x240, 2.9 s at 640x480 and 7.8 s at 1280x720. pub width: u32, pub height: u32, - /// Fotogramas descartados para que la exposición automática se asiente. + /// Frames discarded so auto-exposure can settle. pub warmup_frames: u32, pub timeout_secs: u64, - /// Carpeta donde guardar cada fotograma capturado. Vacío = no se guarda - /// ninguno, que es lo que corresponde por defecto. + /// Directory where each captured frame is saved. Empty = none is saved, + /// which is the right default. pub save_dir: String, } @@ -431,28 +427,27 @@ impl Default for CameraConfig { } } -/// Mirar la pantalla. +/// Looking at the screen. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct ScreenConfig { pub enabled: bool, - /// Programa de captura. `{ancho}` y `{salida}` se sustituyen antes de - /// ejecutar, y tiene que escribir un JPEG por la salida estándar. Está - /// fuera del binario para que añadir un compositor sea editar un guion. + /// Capture program. `{width}` and `{output}` are substituted before + /// running, and it must write a JPEG to standard output. It lives outside + /// the binary so supporting another compositor means editing a script. pub command: Vec<String>, - /// Ancho al que se reduce la captura antes de mandarla al modelo. + /// Width the capture is scaled down to before sending it to the model. /// - /// **No lo bajes a la ligera.** Medido con tipografía de interfaz de 13 px - /// y preguntando por datos concretos: a 1280 px acierta 3 de 3 en 7,6 s; a - /// 960, 2 de 3; a 640, 1 de 3. Y cuando falla no dice que no lo lee: se - /// inventa el contenido con aplomo. + /// **Do not lower it lightly.** Measured with 13 px UI type and asking for + /// specific details: at 1280 px it gets 3 of 3 in 7.6 s; at 960, 2 of 3; + /// at 640, 1 of 3. And when it fails it does not say it cannot read it: it + /// confidently makes the content up. pub width: u32, - /// Monitor concreto. Vacío = todo lo que haya. + /// A specific monitor. Empty = everything there is. pub output: String, pub timeout_secs: u64, - /// Carpeta donde guardar las capturas. Vacío = no se guarda ninguna, que - /// es lo que corresponde: en una captura caben contraseñas y mensajes - /// privados. + /// Directory where captures are saved. Empty = none is saved, which is + /// right: a capture can hold passwords and private messages. pub save_dir: String, } @@ -461,9 +456,9 @@ impl Default for ScreenConfig { Self { enabled: true, command: vec![ - "scripts/capturar-pantalla.sh".into(), - "{ancho}".into(), - "{salida}".into(), + "scripts/capture-screen.sh".into(), + "{width}".into(), + "{output}".into(), ], width: 1280, output: String::new(), @@ -476,9 +471,9 @@ impl Default for ScreenConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct SupervisorConfig { - /// Lanza los servidores en lugar de suponer que ya están arriba. + /// Starts the servers instead of assuming they are already up. pub manage: bool, - /// Segundos de espera a que un servidor conteste a `/health`. + /// Seconds to wait for a server to answer `/health`. pub startup_timeout_secs: u64, pub llama: LlamaProcess, pub tts: TtsProcess, @@ -501,11 +496,11 @@ pub struct LlamaProcess { pub binary: PathBuf, pub model: PathBuf, pub mmproj: PathBuf, - /// Plantilla de chat que se pasa con `--chat-template-file`. + /// Chat template passed with `--chat-template-file`. /// - /// La del modelo abre `<think>` sin cerrarlo nunca, y el asistente se pasa - /// entonces varios segundos razonando antes de decir la primera palabra. - /// Esta copia deja el bloque cerrado de entrada. + /// The model's own template opens `<think>` and never closes it, so the + /// assistant spends several seconds reasoning before saying the first + /// word. This copy starts with the block already closed. pub chat_template: PathBuf, pub extra_args: Vec<String>, } @@ -560,11 +555,11 @@ pub struct TtsProcess { pub binary: PathBuf, pub model: PathBuf, pub codec: PathBuf, - /// Segundos de audio que el codec acumula antes de decodificar un bloque. + /// Seconds of audio the codec accumulates before decoding a block. /// - /// El valor de fábrica son 24 s, que en la práctica significa «no - /// devuelvas nada hasta terminar la frase entera»: medido, baja el primer - /// audio de 4,9 s a 0,6 s. Es el ajuste con más efecto de todo el sistema. + /// The stock value is 24 s, which in practice means «return nothing until + /// the whole sentence is done»: measured, it lowers the first audio from + /// 4.9 s to 0.6 s. It is the single most effective setting in the system. pub codec_chunk_dur: f32, pub extra_args: Vec<String>, } @@ -588,8 +583,8 @@ impl Config { .map_err(|e| Error::Config(format!("no se pudo leer {}: {e}", path.display())))?; let mut config: Config = toml::from_str(&raw).map_err(|e| Error::Config(format!("{}: {e}", path.display())))?; - // Las rutas relativas se resuelven contra la carpeta del TOML, no - // contra el directorio desde el que se lanza el binario. + // Relative paths are resolved against the TOML directory, not against + // the directory the binary is launched from. if let Some(base) = path.parent().filter(|p| !p.as_os_str().is_empty()) { config.rebase(base); } @@ -597,7 +592,7 @@ impl Config { Ok(config) } - /// Reinterpreta las rutas relativas respecto de `base`. + /// Reinterprets relative paths relative to `base`. pub fn rebase(&mut self, base: &Path) { let fix = |p: &mut PathBuf| { if p.is_relative() { @@ -667,7 +662,7 @@ impl Config { )); } } - "ddgs" | "comando" => { + "ddgs" | "command" | "comando" => { if self.search.command.is_empty() { return Err(Error::Config( "search.backend = «ddgs» necesita search.command".into(), diff --git a/crates/asist-core/src/error.rs b/crates/asist-core/src/error.rs index b559bee..f0dfbe2 100644 --- a/crates/asist-core/src/error.rs +++ b/crates/asist-core/src/error.rs @@ -2,12 +2,12 @@ use std::fmt; pub type Result<T> = std::result::Result<T, Error>; -/// Un fallo de una etapa del pipeline. El orquestador decide si es fatal o si -/// basta con abortar el turno en curso, así que el error lleva esa distinción -/// en lugar de dejarla al criterio de quien lo recibe. +/// A failure in a pipeline stage. The orchestrator decides whether it is +/// fatal or whether aborting the current turn is enough, so the error carries +/// that distinction instead of leaving it to whoever receives it. #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("configuración inválida: {0}")] + #[error("invalid configuration: {0}")] Config(String), #[error("audio: {0}")] @@ -22,24 +22,24 @@ pub enum Error { #[error("TTS: {0}")] Tts(String), - #[error("HTTP {status} en {url}: {body}")] + #[error("HTTP {status} at {url}: {body}")] Http { status: u16, url: String, body: String, }, - #[error("transporte hacia {url}: {source}")] + #[error("transport to {url}: {source}")] Transport { url: String, #[source] source: std::io::Error, }, - #[error("herramienta «{tool}»: {message}")] + #[error("tool «{tool}»: {message}")] Tool { tool: String, message: String }, - #[error("operación cancelada")] + #[error("operation cancelled")] Cancelled, #[error(transparent)] @@ -50,17 +50,17 @@ pub enum Error { } impl Error { - /// Un error fatal tumba el asistente; el resto sólo aborta el turno. + /// A fatal error brings the assistant down; the rest only abort the turn. /// - /// Los servidores locales se caen y se reinician, y el usuario prefiere - /// oír «no te he entendido» a que el proceso muera, así que sólo la - /// configuración y el audio (que no se pueden reintentar) son fatales. + /// The local servers crash and restart, and the user would rather hear «no + /// te he entendido» than have the process die, so only configuration and + /// audio (which cannot be retried) are fatal. pub fn is_fatal(&self) -> bool { matches!(self, Error::Config(_) | Error::Audio(_)) } - /// `true` cuando reintentar tiene sentido: el servidor aún está cargando - /// el modelo, o se ha caído la conexión a mitad de una petición. + /// `true` when retrying makes sense: the server is still loading the + /// model, or the connection dropped mid-request. pub fn is_retryable(&self) -> bool { match self { Error::Transport { .. } => true, @@ -70,7 +70,7 @@ impl Error { } } -/// Contexto legible para los `Result` que cruzan una frontera de crate. +/// Readable context for `Result`s that cross a crate boundary. pub trait Context<T> { fn ctx(self, f: impl FnOnce() -> String) -> Result<T>; } diff --git a/crates/asist-core/src/event.rs b/crates/asist-core/src/event.rs index 549bec0..bf9a6e6 100644 --- a/crates/asist-core/src/event.rs +++ b/crates/asist-core/src/event.rs @@ -1,9 +1,9 @@ use std::time::{Duration, Instant}; -/// Identifica un turno de conversación (una intervención del usuario y la -/// respuesta que provoca). Todo lo que viaja por el bus lo lleva, para que un -/// resultado que llega tarde no se confunda con el turno que ya está en curso -/// —el caso típico cuando el usuario interrumpe al asistente. +/// Identifies a conversation turn (one user utterance and the answer it +/// triggers). Everything travelling on the bus carries it, so a result that +/// arrives late is not mistaken for the turn in progress, the typical case +/// being the user interrupting the assistant. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct TurnId(pub u64); @@ -13,32 +13,32 @@ impl std::fmt::Display for TurnId { } } -/// Motivo por el que se corta la reproducción en curso. +/// Why the current playback is cut. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InterruptReason { - /// El usuario ha empezado a hablar encima del asistente (barge-in). + /// The user started talking over the assistant (barge-in). UserSpoke, - /// Petición explícita: tecla, señal o cierre. + /// Explicit request: key, signal or shutdown. Requested, } -/// Todo lo que las etapas se cuentan entre sí. Un único enum mantiene el bus -/// observable: el renderizador y la telemetría ven exactamente los mismos -/// eventos que el orquestador, sin canales paralelos que se desincronicen. +/// Everything the stages tell each other. A single enum keeps the bus +/// observable: the renderer and the telemetry see exactly the same events as +/// the orchestrator, with no parallel channels that fall out of sync. #[derive(Debug, Clone)] pub enum Event { - /// El VAD ha detectado el arranque de una intervención. + /// The VAD detected the start of an utterance. SpeechStarted { turn: TurnId, at: Instant }, - /// Transcripción provisional de la ventana deslizante: `committed` ya es - /// estable, `volatile` todavía puede cambiar. + /// Partial transcription of the sliding window: `committed` is already + /// stable, `volatile` may still change. Partial { turn: TurnId, committed: String, volatile: String, }, - /// Transcripción definitiva de la intervención completa. + /// Final transcription of the whole utterance. Transcript { turn: TurnId, text: String, @@ -46,33 +46,33 @@ pub enum Event { decode: Duration, }, - /// La intervención no contenía nada transcribible. + /// The utterance had nothing transcribable. Discarded { turn: TurnId }, - /// Primer fragmento de texto que devuelve el modelo. + /// First text fragment returned by the model. ReplyStarted { turn: TurnId, ttft: Duration }, - /// Un trozo de respuesta según llega del modelo. + /// A piece of the answer as it arrives from the model. ReplyDelta { turn: TurnId, text: String }, - /// Una frase completa lista para sintetizar. + /// A complete sentence ready to be synthesized. Sentence { turn: TurnId, index: usize, text: String, }, - /// Respuesta completa del modelo para este turno. + /// Complete model answer for this turn. ReplyDone { turn: TurnId, text: String }, - /// El modelo ha pedido ejecutar una herramienta. + /// The model asked to run a tool. ToolRequested { turn: TurnId, name: String, arguments: String, }, - /// Resultado de esa ejecución. + /// Result of that run. ToolFinished { turn: TurnId, name: String, @@ -81,22 +81,22 @@ pub enum Event { took: Duration, }, - /// Primer audio audible del turno: la métrica que de verdad percibe quien habla. + /// First audible audio of the turn: the metric the speaker really perceives. AudioStarted { turn: TurnId, latency: Duration }, - /// Se ha terminado de reproducir todo lo del turno. + /// Everything of the turn has finished playing. AudioFinished { turn: TurnId }, - /// Se ha cortado la reproducción. + /// Playback was cut. Interrupted { turn: TurnId, reason: InterruptReason, }, - /// Aviso no fatal; el turno continúa. + /// Non-fatal warning; the turn continues. Warning { turn: TurnId, message: String }, - /// Fallo que aborta el turno. + /// Failure that aborts the turn. Failed { turn: TurnId, message: String }, /// Cierre ordenado. diff --git a/crates/asist-core/src/http.rs b/crates/asist-core/src/http.rs index 32b6680..3273bb8 100644 --- a/crates/asist-core/src/http.rs +++ b/crates/asist-core/src/http.rs @@ -1,12 +1,12 @@ -//! Cliente HTTP/1.1 mínimo para hablar con los servidores locales. +//! Minimal HTTP/1.1 client to talk to the local servers. //! -//! Los tres motores (llama-server, tts-server) escuchan en localhost sobre HTTP -//! plano, así que no hace falta TLS ni un runtime asíncrono. A cambio de las -//! ~300 líneas de aquí se gana lo único que ninguna librería genérica da -//! cómodo y que este pipeline necesita de verdad: **abortar una respuesta a -//! media descarga**. Cuando el usuario interrumpe al asistente hay que dejar de -//! leer audio ya generado y cerrar el socket en ese mismo instante; con un -//! cliente que sólo expone `read_to_end` eso no se puede hacer. +//! The engines (llama-server, tts-server) listen on localhost over plain +//! HTTP, so neither TLS nor an async runtime is needed. In exchange for the +//! ~300 lines here we get the one thing no generic library makes easy and +//! this pipeline really needs: **aborting a response mid-download**. When the +//! user interrupts the assistant, reading already-generated audio must stop +//! and the socket must close that very instant; a client that only exposes +//! `read_to_end` cannot do that. use std::io::{BufRead, BufReader, Read, Write}; use std::net::TcpStream; @@ -16,11 +16,11 @@ use std::time::Duration; use crate::error::{Error, Result}; -/// Bandera compartida que aborta una lectura en curso. +/// Shared flag that aborts a read in progress. /// -/// Se comprueba entre bloques, así que el corte ocurre como muy tarde un -/// `read` después de activarla —del orden de milisegundos con los tamaños de -/// bloque que usan los servidores. +/// It is checked between blocks, so the cut happens at most one `read` after +/// it is set, in the order of milliseconds with the block sizes the servers +/// use. #[derive(Clone, Default)] pub struct Cancel(Arc<AtomicBool>); @@ -44,11 +44,11 @@ impl Cancel { #[derive(Clone, Debug)] pub struct HttpClient { - /// `host:puerto` del servidor local. + /// `host:port` of the local server. pub authority: String, pub connect_timeout: Duration, - /// Tiempo máximo sin recibir un solo byte. No es el tiempo total: una - /// síntesis larga puede tardar minutos siempre que siga fluyendo. + /// Maximum time without receiving a single byte. It is not the total time: + /// a long synthesis can take minutes as long as it keeps flowing. pub read_timeout: Duration, } @@ -75,7 +75,7 @@ impl HttpClient { } })?; stream.set_read_timeout(Some(self.read_timeout))?; - // Los cuerpos son pequeños y la latencia manda: nada de Nagle. + // Bodies are small and latency rules: no Nagle. let _ = stream.set_nodelay(true); Ok(stream) } @@ -106,22 +106,22 @@ impl HttpClient { Response::read_head(BufReader::new(stream), url) } - /// GET que devuelve el cuerpo completo ya decodificado. + /// GET returning the whole, already decoded body. pub fn get(&self, path: &str) -> Result<Vec<u8>> { self.send("GET", path, None)?.into_body() } - /// POST de JSON que devuelve el cuerpo completo. + /// JSON POST returning the whole body. pub fn post_json(&self, path: &str, body: &serde_json::Value) -> Result<Vec<u8>> { let payload = serde_json::to_vec(body)?; self.send("POST", path, Some(&payload))?.into_body() } - /// POST de JSON cuya respuesta se consume a trozos según llega. + /// JSON POST whose response is consumed in chunks as it arrives. /// - /// `on_chunk` recibe cada bloque en cuanto está disponible y devuelve - /// `false` para cortar; junto a `cancel` son las dos vías por las que el - /// barge-in detiene una síntesis a medias. + /// `on_chunk` gets each block as soon as it is available and returns + /// `false` to stop; together with `cancel` they are the two ways barge-in + /// stops a synthesis midway. pub fn post_json_streaming( &self, path: &str, @@ -135,7 +135,7 @@ impl HttpClient { response.stream(cancel, &mut on_chunk) } - /// POST de JSON que entrega la respuesta línea a línea (SSE de llama-server). + /// JSON POST that delivers the response line by line (llama-server SSE). pub fn post_json_lines( &self, path: &str, @@ -157,7 +157,7 @@ impl HttpClient { }) } - /// Sondea `/health` y devuelve `true` si el servidor está listo. + /// Polls `/health` and returns `true` if the server is |