pub mod audio; pub mod clipboard; pub mod diagnostics; pub mod feedback; pub mod fs; pub mod hardware; pub mod hotkey; pub mod intentions; pub mod live; pub mod llm; pub mod meeting; pub mod models; pub mod nudges; pub mod onboarding; pub mod paste; pub mod power; pub mod profiles; pub mod rituals; pub mod security; pub mod tasks; pub mod transcription; pub mod transcripts; pub mod tts; pub mod update; pub mod windows; /// Build the Whisper `initial_prompt` for a transcription request. /// /// Precedence: /// 1. Caller-supplied `request_prompt` (non-empty wins outright — the caller /// has already made the decision). /// 2. Profile's stored prompt + profile terms (joined: the prompt frames the /// task, the vocabulary biases recognition toward domain terms). /// 3. Profile prompt alone, or vocabulary alone. /// 4. `None` if nothing is set. /// /// Feeding `profile_terms` into `initial_prompt` (the OpenWhispr pattern) lets /// whisper.cpp bias its decoder toward the correct spelling of user-specific /// vocabulary at decode time, before any LLM cleanup pass. pub fn build_initial_prompt( request_prompt: &str, profile_prompt: &str, profile_terms: &[String], ) -> Option { let trimmed_request = request_prompt.trim(); if !trimmed_request.is_empty() { return Some(trimmed_request.to_string()); } let trimmed_profile = profile_prompt.trim(); let terms_list = profile_terms .iter() .map(|term| term.trim()) .filter(|term| !term.is_empty()) .collect::>() .join(", "); match (trimmed_profile.is_empty(), terms_list.is_empty()) { (true, true) => None, (false, true) => Some(trimmed_profile.to_string()), (true, false) => Some(format!("Vocabulary: {terms_list}.")), (false, false) => Some(format!("{trimmed_profile} Vocabulary: {terms_list}.")), } } #[cfg(test)] mod tests { use super::build_initial_prompt; #[test] fn caller_prompt_overrides_everything() { let got = build_initial_prompt( "caller wins", "profile prompt", &["Wren".into(), "CORBEL".into()], ); assert_eq!(got.as_deref(), Some("caller wins")); } #[test] fn profile_prompt_and_terms_are_joined() { let got = build_initial_prompt( "", "You are a meeting notes assistant.", &["Wren".into(), "CORBEL".into()], ); assert_eq!( got.as_deref(), Some("You are a meeting notes assistant. Vocabulary: Wren, CORBEL."), ); } #[test] fn terms_only_produces_vocabulary_sentence() { let got = build_initial_prompt("", "", &["Wren".into(), "CORBEL".into()]); assert_eq!(got.as_deref(), Some("Vocabulary: Wren, CORBEL.")); } #[test] fn profile_prompt_alone_is_passed_through() { let got = build_initial_prompt("", "Be concise.", &[]); assert_eq!(got.as_deref(), Some("Be concise.")); } #[test] fn all_empty_returns_none() { assert_eq!(build_initial_prompt("", "", &[]), None); } #[test] fn whitespace_only_terms_are_skipped() { let got = build_initial_prompt("", "", &[" ".into(), "Wren".into(), "".into()]); assert_eq!(got.as_deref(), Some("Vocabulary: Wren.")); } }