use anyhow::Result;
use rustyline::{error::ReadlineError, DefaultEditor};
use std::time::Duration;
use tokio::time::timeout;
pub enum InputMethod {
Text(String),
Voice(String),
Image(String),
Gesture(String),
Modal(String),
}
pub struct UniversalInput {
editor: DefaultEditor,
voice_enabled: bool,
vision_enabled: bool,
input_timeout: Duration,
show_contextual_hints: bool,
}
impl UniversalInput {
pub fn new() -> Result<Self> {
Ok(Self {
editor: DefaultEditor::new()?,
voice_enabled: false,
vision_enabled: false,
input_timeout: Duration::from_secs(30), show_contextual_hints: true,
})
}
pub fn enable_voice(&mut self, enabled: bool) {
self.voice_enabled = enabled;
}
pub fn enable_vision(&mut self, enabled: bool) {
self.vision_enabled = enabled;
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.input_timeout = timeout;
}
pub fn set_contextual_hints(&mut self, enabled: bool) {
self.show_contextual_hints = enabled;
}
pub fn read(&mut self, prompt: &str) -> Result<InputMethod> {
if self.show_contextual_hints {
self.show_input_hints(prompt);
}
let result = match self.editor.readline(prompt) {
Ok(line) => {
if line.starts_with("VOICE:") {
let voice_text = line.strip_prefix("VOICE:").unwrap_or(&line).trim();
InputMethod::Voice(voice_text.to_string())
}
else if line.starts_with("IMAGE:") {
let image_desc = line.strip_prefix("IMAGE:").unwrap_or(&line).trim();
InputMethod::Image(image_desc.to_string())
}
else {
InputMethod::Text(line)
}
}
Err(ReadlineError::Interrupted) => InputMethod::Text(String::new()),
Err(ReadlineError::Eof) => InputMethod::Text("exit".into()),
Err(err) => return Err(err.into()),
};
Ok(result)
}
fn show_input_hints(&self, prompt: &str) {
if prompt.contains("review") || prompt.contains("code") {
println!("💡 Hint: You can type normally, or prefix with 'VOICE:' for voice simulation, 'IMAGE:' for visual context");
}
}
pub fn add_history(&mut self, entry: &str) -> Result<()> {
self.editor.add_history_entry(entry)?;
Ok(())
}
pub fn status(&self) -> InputStatus {
InputStatus {
voice_enabled: self.voice_enabled,
vision_enabled: self.vision_enabled,
supported_modalities: vec![
InputModality::Text,
InputModality::Voice,
InputModality::Image,
],
timeout_duration: self.input_timeout,
}
}
pub fn simulate_voice_input(&mut self, text: &str) -> Result<InputMethod> {
if self.voice_enabled {
Ok(InputMethod::Voice(text.to_string()))
} else {
Ok(InputMethod::Text(text.to_string()))
}
}
pub fn simulate_image_input(&mut self, description: &str) -> Result<InputMethod> {
if self.vision_enabled {
Ok(InputMethod::Image(description.to_string()))
} else {
Ok(InputMethod::Text(format!(
"/ask Describe this: {}",
description
)))
}
}
}
pub struct InputStatus {
pub voice_enabled: bool,
pub vision_enabled: bool,
pub supported_modalities: Vec<InputModality>,
pub timeout_duration: Duration,
}
#[derive(Debug, Clone)]
pub enum InputModality {
Text,
Voice,
Image,
Gesture,
Keyboard,
Touch,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_universal_input_creation() {
let input = UniversalInput::new();
assert!(input.is_ok());
}
#[test]
fn test_voice_simulation() {
let mut input = UniversalInput::new().unwrap();
input.enable_voice(true);
let result = input.simulate_voice_input("Test voice command");
assert!(matches!(result, Ok(InputMethod::Voice(_))));
}
#[test]
fn test_image_simulation() {
let mut input = UniversalInput::new().unwrap();
input.enable_vision(true);
let result = input.simulate_image_input("A code screenshot");
assert!(matches!(result, Ok(InputMethod::Image(_))));
}
}