use crate::session::history::{append_to_session_history_file, load_session_history_from_file};
use anyhow::Result;
use colored::*;
use rustyline::error::ReadlineError;
use rustyline::{
Cmd, ConditionalEventHandler, Event, EventHandler, KeyEvent, Modifiers, RepeatCount,
};
use rustyline::{CompletionType, Config as RustylineConfig, EditMode, Editor};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[derive(Debug)]
pub enum InputResult {
Text(String),
Cancelled,
Exit,
AddWithoutSending(String),
}
struct SmartCtrlEHandler;
impl ConditionalEventHandler for SmartCtrlEHandler {
fn handle(
&self,
_evt: &Event,
_n: RepeatCount,
_positive: bool,
ctx: &rustyline::EventContext,
) -> Option<Cmd> {
if ctx.has_hint() {
Some(Cmd::CompleteHint)
} else {
None
}
}
}
struct CtrlGHandler {
add_without_sending: Arc<AtomicBool>,
}
impl ConditionalEventHandler for CtrlGHandler {
fn handle(
&self,
_evt: &Event,
_n: RepeatCount,
_positive: bool,
_ctx: &rustyline::EventContext,
) -> Option<Cmd> {
self.add_without_sending.store(true, Ordering::SeqCst);
Some(Cmd::AcceptLine)
}
}
use crate::log_info;
pub fn read_user_input(
estimated_cost: f64,
octomind_config: &crate::config::Config,
role: &str,
) -> Result<InputResult> {
let add_without_sending = Arc::new(AtomicBool::new(false));
let config = RustylineConfig::builder()
.completion_type(CompletionType::List) .edit_mode(EditMode::Emacs)
.auto_add_history(true) .bell_style(rustyline::config::BellStyle::None) .max_history_size(1000)? .color_mode(rustyline::ColorMode::Enabled) .build();
let mut editor = Editor::with_config(config)?;
use crate::session::chat_helper::CommandHelper;
editor.set_helper(Some(CommandHelper::new(octomind_config, role)));
editor.bind_sequence(
Event::KeySeq(vec![KeyEvent::new('e', Modifiers::CTRL)]),
EventHandler::Conditional(Box::new(SmartCtrlEHandler)),
);
editor.bind_sequence(
Event::KeySeq(vec![KeyEvent::new('\t', Modifiers::empty())]),
EventHandler::Simple(Cmd::Complete),
);
editor.bind_sequence(
Event::KeySeq(vec![KeyEvent::new('\t', Modifiers::SHIFT)]),
EventHandler::Simple(Cmd::ReverseSearchHistory),
);
editor.bind_sequence(
Event::KeySeq(vec![
KeyEvent::new('\x1b', Modifiers::empty()),
KeyEvent::new('[', Modifiers::empty()),
KeyEvent::new('C', Modifiers::empty()),
]),
EventHandler::Simple(Cmd::CompleteHint),
);
editor.bind_sequence(
Event::KeySeq(vec![
KeyEvent::new('\x1b', Modifiers::empty()),
KeyEvent::new('[', Modifiers::empty()),
KeyEvent::new('C', Modifiers::empty()),
]),
EventHandler::Simple(Cmd::CompleteHint),
);
editor.bind_sequence(
Event::KeySeq(vec![KeyEvent::new('j', Modifiers::CTRL)]),
EventHandler::Simple(Cmd::Newline),
);
let add_without_sending_clone = add_without_sending.clone();
editor.bind_sequence(
Event::KeySeq(vec![KeyEvent::new('g', Modifiers::CTRL)]),
EventHandler::Conditional(Box::new(CtrlGHandler {
add_without_sending: add_without_sending_clone,
})),
);
match load_session_history_from_file(role) {
Ok(history_lines) => {
for line in history_lines {
let _ = editor.add_history_entry(line);
}
}
Err(e) => {
log_info!("Could not load history for role '{}': {}", role, e);
}
}
let prompt = if estimated_cost > 0.0 {
format!("[~${:.2}] > ", estimated_cost)
.bright_blue()
.to_string()
} else {
"> ".bright_blue().to_string()
};
match editor.readline(&prompt) {
Ok(line) => {
let is_add_without_sending = add_without_sending.load(Ordering::SeqCst);
let _ = editor.add_history_entry(line.clone());
if let Err(e) = append_to_session_history_file(role, &line) {
log_info!(
"Could not append to history file for role '{}': {}",
role,
e
);
}
if !line.trim().starts_with('/') {
let _ = crate::session::logger::log_user_request(&line);
}
if is_add_without_sending {
Ok(InputResult::AddWithoutSending(line))
} else {
Ok(InputResult::Text(line))
}
}
Err(ReadlineError::Interrupted) => {
Ok(InputResult::Cancelled)
}
Err(ReadlineError::Eof) => {
println!("\nExiting session...");
if let Ok(sessions_dir) = crate::session::get_sessions_dir() {
println!("Session files saved in: {}", sessions_dir.display());
}
log_info!("Session preserved for future reference.");
Ok(InputResult::Exit)
}
Err(err) => {
println!("Error: {:?}", err);
Ok(InputResult::Text(String::new()))
}
}
}