Release 1.1: Pre-meeting models, Calendar improvements, Logs, Compact Recorder
This commit is contained in:
@@ -131,8 +131,15 @@ impl AudioProcessor {
|
||||
while self.vad_buffer.len() >= self.vad_chunk_size {
|
||||
let vad_chunk: Vec<f32> = self.vad_buffer.drain(0..self.vad_chunk_size).collect();
|
||||
// Run Detection
|
||||
let probability = self.vad.predict(vad_chunk);
|
||||
let is_speech = probability > 0.5;
|
||||
// Run Detection
|
||||
let probability = self.vad.predict(vad_chunk.clone());
|
||||
|
||||
// Calculate RMS for this chunk to use as fallback/hybrid detection
|
||||
let sq_sum: f32 = vad_chunk.iter().map(|x| x * x).sum();
|
||||
let rms = (sq_sum / vad_chunk.len() as f32).sqrt();
|
||||
|
||||
// Hybrid VAD: Probability > 0.4 OR RMS > 0.005 (approx -46dB)
|
||||
let is_speech = probability > 0.4 || rms > 0.005;
|
||||
|
||||
if is_speech {
|
||||
self.is_speech_active = true;
|
||||
@@ -141,8 +148,14 @@ impl AudioProcessor {
|
||||
|
||||
// Emit VAD event periodically (every 500ms)
|
||||
if self.last_event_time.elapsed().as_millis() > 500 {
|
||||
// Calculate simple RMS of the current chunk for debugging
|
||||
let sq_sum: f32 = vad_chunk.iter().map(|x| x * x).sum();
|
||||
let rms = (sq_sum / vad_chunk.len() as f32).sqrt();
|
||||
|
||||
// Print debug info to stdout (viewable in terminal)
|
||||
println!("VAD Debug: Prob={:.4}, RMS={:.6}, Speech={}", probability, rms, is_speech);
|
||||
|
||||
if let Some(app) = &self.app_handle {
|
||||
// Calculate crude RMS for visualization or just send probability
|
||||
// Just sending probability is enough for now
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
struct VadEvent {
|
||||
|
||||
96
src-tauri/src/email.rs
Normal file
96
src-tauri/src/email.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use tauri::AppHandle;
|
||||
use lettre::{Message, SmtpTransport, Transport, AsyncTransport};
|
||||
use lettre::transport::smtp::authentication::Credentials;
|
||||
use lettre::transport::smtp::AsyncSmtpTransport;
|
||||
use lettre::Tokio1Executor;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct SmtpConfig {
|
||||
host: String,
|
||||
port: u16,
|
||||
username: String,
|
||||
password: String,
|
||||
sender_email: String,
|
||||
sender_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct EmailMessage {
|
||||
to: Vec<String>,
|
||||
subject: String,
|
||||
body_html: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send_smtp_email(app: AppHandle, config: SmtpConfig, message: EmailMessage) -> Result<String, String> {
|
||||
println!("SMTP: Preparing to send email to {:?}", message.to);
|
||||
|
||||
// 1. Build Message
|
||||
let sender_str = if let Some(name) = &config.sender_name {
|
||||
format!("{} <{}>", name, config.sender_email)
|
||||
} else {
|
||||
config.sender_email.clone()
|
||||
};
|
||||
|
||||
let mut builder = Message::builder()
|
||||
.from(sender_str.parse().map_err(|e: lettre::address::AddressError| e.to_string())?)
|
||||
.subject(message.subject);
|
||||
|
||||
for recipient in message.to {
|
||||
builder = builder.to(recipient.parse().map_err(|e: lettre::address::AddressError| e.to_string())?);
|
||||
}
|
||||
|
||||
let email = builder
|
||||
.header(lettre::message::header::ContentType::TEXT_HTML)
|
||||
.body(message.body_html)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 2. Build Transport
|
||||
println!("SMTP: Connecting to {}:{}...", config.host, config.port);
|
||||
let creds = Credentials::new(config.username, config.password);
|
||||
|
||||
// TLS Configuration
|
||||
// Proton Mail and others on 465 require Implicit TLS (Wrapper).
|
||||
// Others on 587 use STARTTLS (Opportunistic).
|
||||
|
||||
use lettre::transport::smtp::client::{Tls, TlsParameters};
|
||||
|
||||
let tls_params = TlsParameters::builder(config.host.clone())
|
||||
.build()
|
||||
.map_err(|e| format!("TLS Params error: {}", e))?;
|
||||
|
||||
let builder = AsyncSmtpTransport::<Tokio1Executor>::relay(&config.host)
|
||||
.map_err(|e| e.to_string())?
|
||||
.port(config.port)
|
||||
.credentials(creds);
|
||||
|
||||
let mailer: AsyncSmtpTransport<Tokio1Executor> = if config.port == 465 {
|
||||
println!("SMTP: Using Implicit TLS (Wrapper) for port 465");
|
||||
builder.tls(Tls::Wrapper(tls_params)).build()
|
||||
} else {
|
||||
println!("SMTP: Using Opportunistic TLS (STARTTLS) for port {}", config.port);
|
||||
builder.tls(Tls::Opportunistic(tls_params)).build()
|
||||
};
|
||||
|
||||
// 3. Send with Timeout
|
||||
println!("SMTP: Sending...");
|
||||
let send_task = mailer.send(email);
|
||||
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(15), send_task).await {
|
||||
Ok(result) => match result {
|
||||
Ok(_) => {
|
||||
println!("SMTP: Success!");
|
||||
Ok("Email sent successfully".to_string())
|
||||
},
|
||||
Err(e) => {
|
||||
println!("SMTP: Failed: {}", e);
|
||||
// Hint at common issues
|
||||
Err(format!("Failed to send: {}. (Check Host/Port/Password)", e))
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
println!("SMTP: Timeout");
|
||||
Err("Connection timed out after 15s. Try changing Port (465 vs 587) or check VPN/Firewall.".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use tokio::time::sleep;
|
||||
mod audio_processor;
|
||||
use audio_processor::AudioProcessor;
|
||||
mod auth;
|
||||
mod email;
|
||||
|
||||
// State to hold the active recording stream
|
||||
struct AppState {
|
||||
@@ -625,9 +626,26 @@ async fn save_text_file(app: AppHandle, path: String, content: String) -> Result
|
||||
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
async fn read_log_file(app: AppHandle) -> Result<String, String> {
|
||||
let log_path = app.path().app_log_dir().map_err(|e| e.to_string())?.join("hearbit-ai.log");
|
||||
if log_path.exists() {
|
||||
let content = std::fs::read_to_string(&log_path).map_err(|e| e.to_string())?;
|
||||
Ok(content)
|
||||
} else {
|
||||
Ok("No log file found yet.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_log::Builder::default()
|
||||
.targets([
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir { file_name: Some("hearbit-ai.log".to_string()) }),
|
||||
])
|
||||
.build())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
@@ -650,7 +668,9 @@ pub fn run() {
|
||||
create_hearbit_audio_device,
|
||||
auth::start_auth_flow,
|
||||
auth::get_calendar_events,
|
||||
save_text_file
|
||||
save_text_file,
|
||||
read_log_file,
|
||||
email::send_smtp_email
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
Reference in New Issue
Block a user