use anyhow::{Context, Result};
use std::io::Write;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use tokio::time::sleep;
use crate::ipc::{IpcMessage, IpcResponse, IpcServer};
use std::time::Duration;
use whis_core::{
ApiConfig, AudioRecorder, RecordingOutput, copy_to_clipboard, parallel_transcribe,
transcribe_audio,
};
#[derive(Debug, Clone, Copy, PartialEq)]
enum ServiceState {
Idle,
Recording,
Transcribing,
}
pub struct Service {
state: Arc<Mutex<ServiceState>>,
recorder: Arc<Mutex<Option<AudioRecorder>>>,
config: ApiConfig,
recording_counter: Arc<Mutex<u32>>,
}
impl Service {
pub fn new(config: ApiConfig) -> Result<Self> {
Ok(Self {
state: Arc::new(Mutex::new(ServiceState::Idle)),
recorder: Arc::new(Mutex::new(None)),
config,
recording_counter: Arc::new(Mutex::new(0)),
})
}
pub async fn run(&self, hotkey_rx: Option<Receiver<()>>) -> Result<()> {
let ipc_server = IpcServer::new().context("Failed to create IPC server")?;
println!("whis listening. Ctrl+C to stop.");
loop {
if let Some(mut conn) = ipc_server.try_accept()? {
match conn.receive() {
Ok(message) => {
let response = self.handle_message(message).await;
let _ = conn.send(response);
}
Err(e) => {
eprintln!("Error receiving message: {e}");
let _ = conn.send(IpcResponse::Error(e.to_string()));
}
}
}
if let Some(ref rx) = hotkey_rx {
if rx.try_recv().is_ok() {
self.handle_toggle().await;
}
}
sleep(Duration::from_millis(10)).await;
}
}
async fn handle_message(&self, message: IpcMessage) -> IpcResponse {
match message {
IpcMessage::Stop => {
println!("Stop signal received");
tokio::spawn(async {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
std::process::exit(0);
});
IpcResponse::Success
}
IpcMessage::Status => {
let state = *self.state.lock().unwrap();
match state {
ServiceState::Idle => IpcResponse::Idle,
ServiceState::Recording => IpcResponse::Recording,
ServiceState::Transcribing => IpcResponse::Transcribing,
}
}
}
}
async fn handle_toggle(&self) -> IpcResponse {
let current_state = *self.state.lock().unwrap();
match current_state {
ServiceState::Idle => {
let count = {
let mut c = self.recording_counter.lock().unwrap();
*c += 1;
*c
};
match self.start_recording().await {
Ok(_) => {
print!("#{count} recording...");
let _ = std::io::stdout().flush();
IpcResponse::Recording
}
Err(e) => {
println!("#{count} error: {e}");
IpcResponse::Error(e.to_string())
}
}
}
ServiceState::Recording => {
*self.state.lock().unwrap() = ServiceState::Transcribing;
let count = *self.recording_counter.lock().unwrap();
print!("\r#{count} transcribing...");
let _ = std::io::stdout().flush();
match self.stop_and_transcribe().await {
Ok(_) => {
*self.state.lock().unwrap() = ServiceState::Idle;
println!("\r#{count} done ");
IpcResponse::Success
}
Err(e) => {
*self.state.lock().unwrap() = ServiceState::Idle;
println!("\r#{count} error: {e}");
IpcResponse::Error(e.to_string())
}
}
}
ServiceState::Transcribing => {
IpcResponse::Transcribing
}
}
}
async fn start_recording(&self) -> Result<()> {
let mut recorder = AudioRecorder::new()?;
recorder.start_recording()?;
*self.recorder.lock().unwrap() = Some(recorder);
*self.state.lock().unwrap() = ServiceState::Recording;
Ok(())
}
async fn stop_and_transcribe(&self) -> Result<()> {
let mut recorder = self
.recorder
.lock()
.unwrap()
.take()
.context("No active recording")?;
let recording_data = recorder.stop_recording()?;
let audio_result = tokio::task::spawn_blocking(move || recording_data.finalize())
.await
.context("Failed to join task")??;
let api_key = self.config.openai_api_key.clone();
let transcription = match audio_result {
RecordingOutput::Single(audio_data) => {
tokio::task::spawn_blocking(move || transcribe_audio(&api_key, audio_data))
.await
.context("Failed to join task")??
}
RecordingOutput::Chunked(chunks) => {
parallel_transcribe(&api_key, chunks, None).await?
}
};
tokio::task::spawn_blocking(move || copy_to_clipboard(&transcription))
.await
.context("Failed to join task")??;
Ok(())
}
}