Skip to main content

codetether_agent/voice/
dictation.rs

1//! Speech-to-text dictation and command extraction.
2
3use serde::{Deserialize, Serialize};
4
5/// A parsed voice command.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct VoiceCommand {
8    pub intent: VoiceIntent,
9    pub raw_transcript: String,
10    pub parameters: Vec<String>,
11}
12
13/// Recognized voice intents.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15#[serde(rename_all = "snake_case")]
16pub enum VoiceIntent {
17    /// Run an autonomous task.
18    AutoPilot,
19    /// Fix a specific issue.
20    FixIssue,
21    /// Review a PR.
22    ReviewPr,
23    /// Ask a question.
24    Question,
25    /// Status check.
26    Status,
27    /// Unknown / raw dictation.
28    Dictation,
29}
30
31/// Parse a voice transcript into a structured command.
32pub fn parse_voice_command(transcript: &str) -> VoiceCommand {
33    let lower = transcript.to_lowercase();
34    let (intent, params) = if lower.starts_with("auto pilot") || lower.starts_with("autopilot") {
35        (VoiceIntent::AutoPilot, extract_params(&lower, "auto pilot"))
36    } else if lower.starts_with("fix issue") {
37        (VoiceIntent::FixIssue, extract_params(&lower, "fix issue"))
38    } else if lower.starts_with("review") {
39        (VoiceIntent::ReviewPr, extract_params(&lower, "review"))
40    } else if lower.starts_with("status") || lower.starts_with("what's the status") {
41        (VoiceIntent::Status, vec!["current".to_string()])
42    } else if lower.starts_with("ask") || lower.ends_with('?') {
43        (VoiceIntent::Question, vec![transcript.to_string()])
44    } else {
45        (VoiceIntent::Dictation, vec![transcript.to_string()])
46    };
47    VoiceCommand {
48        intent,
49        raw_transcript: transcript.to_string(),
50        parameters: params,
51    }
52}
53
54fn extract_params(text: &str, prefix: &str) -> Vec<String> {
55    text.strip_prefix(prefix)
56        .map(|rest| rest.split_whitespace().map(String::from).collect())
57        .unwrap_or_default()
58}