diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs new file mode 100644 index 0000000..225b504 --- /dev/null +++ b/src-tauri/src/app_state.rs @@ -0,0 +1,83 @@ +use std::sync::Mutex; + +use serde::{Deserialize, Serialize}; + +use crate::audio::{RecordingSession, RecordingStatus}; +use crate::models::{model_statuses, ModelWithStatus}; +use crate::settings::AppSettings; + +#[derive(Debug)] +pub struct ManagedAppState { + pub settings: Mutex, + pub recording: Mutex, + pub transcript: Mutex, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct TranscriptDraft { + pub text: String, + pub last_message: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AppSnapshot { + pub settings: AppSettings, + pub recording_status: RecordingStatus, + pub transcript: TranscriptDraft, + pub models: Vec, +} + +impl Default for ManagedAppState { + fn default() -> Self { + Self { + settings: Mutex::new(AppSettings::default()), + recording: Mutex::new(RecordingSession::default()), + transcript: Mutex::new(TranscriptDraft::default()), + } + } +} + +impl ManagedAppState { + pub fn snapshot(&self) -> Result { + let settings = self + .settings + .lock() + .map_err(|_| "falha ao acessar configurações".to_string())? + .clone(); + let recording_status = self + .recording + .lock() + .map_err(|_| "falha ao acessar estado de gravação".to_string())? + .status + .clone(); + let transcript = self + .transcript + .lock() + .map_err(|_| "falha ao acessar transcrição".to_string())? + .clone(); + + Ok(AppSnapshot { + settings, + recording_status, + transcript, + models: model_statuses(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_snapshot_starts_idle() { + let state = ManagedAppState::default(); + let snapshot = state.snapshot().expect("snapshot should work"); + + assert_eq!(snapshot.recording_status, RecordingStatus::Idle); + assert!(snapshot.transcript.text.is_empty()); + assert_eq!(snapshot.models.len(), 1); + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 39f56de..89bb0da 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,13 +1,29 @@ -#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub mod app_state; pub mod audio; pub mod error; pub mod injection; pub mod models; pub mod settings; pub mod transcription; +pub mod ui_commands; + +use app_state::ManagedAppState; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) + .manage(ManagedAppState::default()) + .invoke_handler(tauri::generate_handler![ + ui_commands::get_app_snapshot, + ui_commands::start_recording, + ui_commands::stop_recording_and_transcribe, + ui_commands::copy_ready_text, + ui_commands::request_insert_text, + ui_commands::list_models, + ui_commands::get_settings, + ui_commands::save_settings + ]) .run(tauri::generate_context!()) .expect("erro ao executar o aplicativo Tauri"); } diff --git a/src-tauri/src/ui_commands.rs b/src-tauri/src/ui_commands.rs new file mode 100644 index 0000000..faa7607 --- /dev/null +++ b/src-tauri/src/ui_commands.rs @@ -0,0 +1,136 @@ +use std::path::PathBuf; + +use tauri::State; + +use crate::app_state::{AppSnapshot, ManagedAppState}; +use crate::injection::{FallbackInjector, InsertMethod, InsertRequest, InsertResult, TextInjector}; +use crate::models::{model_statuses, ModelWithStatus}; +use crate::settings::AppSettings; +use crate::transcription::crispasr::{CrispAsrConfig, CrispAsrTranscriber}; +use crate::transcription::{Transcriber, TranscriptionInput, TranscriptionOptions}; + +#[tauri::command] +pub(crate) fn get_app_snapshot(state: State<'_, ManagedAppState>) -> Result { + state.snapshot() +} + +#[tauri::command] +pub(crate) fn start_recording(state: State<'_, ManagedAppState>) -> Result { + state + .recording + .lock() + .map_err(|_| "falha ao acessar estado de gravação".to_string())? + .start() + .map_err(String::from)?; + + state.snapshot() +} + +#[tauri::command] +pub(crate) fn stop_recording_and_transcribe( + state: State<'_, ManagedAppState>, +) -> Result { + let sequence = state + .recording + .lock() + .map_err(|_| "falha ao acessar estado de gravação".to_string())? + .stop() + .map_err(String::from)?; + + let settings = state + .settings + .lock() + .map_err(|_| "falha ao acessar configurações".to_string())? + .clone(); + + let transcriber = CrispAsrTranscriber::new(CrispAsrConfig { + sidecar_name: None, + model_path: None, + }); + + let result = transcriber.transcribe(TranscriptionInput { + audio_path: PathBuf::from(format!("recording-{sequence}.wav")), + options: TranscriptionOptions { + model_id: settings.transcription.model_id, + language: settings.transcription.language, + timestamps: false, + }, + }); + + let mut transcript = state + .transcript + .lock() + .map_err(|_| "falha ao acessar transcrição".to_string())?; + + match result { + Ok(result) => { + transcript.text = result.text; + transcript.last_message = Some("Transcrição concluída.".to_string()); + } + Err(error) => { + transcript.text = "".to_string(); + transcript.last_message = Some(error.to_string()); + } + } + + drop(transcript); + state.snapshot() +} + +#[tauri::command] +pub(crate) fn copy_ready_text(state: State<'_, ManagedAppState>) -> Result { + let transcript = state + .transcript + .lock() + .map_err(|_| "falha ao acessar transcrição".to_string())?; + + Ok(transcript.text.clone()) +} + +#[tauri::command] +pub(crate) fn request_insert_text( + text: String, + state: State<'_, ManagedAppState>, +) -> Result { + let injector = FallbackInjector; + let result = injector.insert_text(InsertRequest { + text, + target: None, + method: InsertMethod::ClipboardPaste, + }); + + state + .transcript + .lock() + .map_err(|_| "falha ao acessar transcrição".to_string())? + .last_message = Some(result.message.clone()); + + Ok(result) +} + +#[tauri::command] +pub(crate) fn list_models() -> Vec { + model_statuses() +} + +#[tauri::command] +pub(crate) fn get_settings(state: State<'_, ManagedAppState>) -> Result { + state + .settings + .lock() + .map_err(|_| "falha ao acessar configurações".to_string()) + .map(|settings| settings.clone()) +} + +#[tauri::command] +pub(crate) fn save_settings( + settings: AppSettings, + state: State<'_, ManagedAppState>, +) -> Result { + *state + .settings + .lock() + .map_err(|_| "falha ao acessar configurações".to_string())? = settings.clone(); + + Ok(settings) +}