summaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs107
1 files changed, 69 insertions, 38 deletions
diff --git a/src/main.rs b/src/main.rs
index 156add8..24e2857 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -23,11 +23,14 @@ use tokio::runtime::Runtime;
const API_URL: &str = "https://api.openai.com/v1/chat/completions";
const MODEL: &str = "gpt-3.5-turbo";
-const HELP: &str = r###".exit Exit the REPL.
-.help Print this help message.
-.role Specify the role that the AI will play.
-
-Press Ctrl+C to abort current chat, Ctrl+D to exit the REPL"###;
+const REPL_COMMANDS: [(&str, &str); 6] = [
+ (".clear", "Clear the screen"),
+ (".clear-history", "Clear the history"),
+ (".exit", " Exit the REPL"),
+ (".help", "Print this help message"),
+ (".history", "Print the history"),
+ (".role", "Specify the role that the AI will play"),
+];
fn main() {
if let Err(err) = start() {
@@ -93,8 +96,37 @@ fn start() -> Result<()> {
}
fn run_repl(runtime: Runtime, client: Client, config: Config, role: Option<String>) -> Result<()> {
- println!("Welcome to aichat {}", env!("CARGO_PKG_VERSION"));
- println!("Type \".help\" for more information.");
+ print_repl_title();
+ let mut commands: Vec<String> = REPL_COMMANDS
+ .into_iter()
+ .map(|(v, _)| v.to_string())
+ .collect();
+ commands.extend(config.roles.iter().map(|v| format!(".role {}", v.name)));
+ let mut completer = DefaultCompleter::with_inclusions(&['.', '-']).set_min_word_len(2);
+ completer.insert(commands.clone());
+ let completer = Box::new(completer);
+ let completion_menu = Box::new(ColumnarMenu::default().with_name("completion_menu"));
+ let mut keybindings = default_emacs_keybindings();
+ keybindings.add_binding(
+ KeyModifiers::NONE,
+ KeyCode::Tab,
+ ReedlineEvent::UntilFound(vec![
+ ReedlineEvent::Menu("completion_menu".to_string()),
+ ReedlineEvent::MenuNext,
+ ]),
+ );
+ let history = Box::new(
+ FileBackedHistory::with_file(1000, get_history_path()?)
+ .map_err(|err| anyhow!("Failed to setup history file, {err}"))?,
+ );
+ let edit_mode = Box::new(Emacs::new(keybindings));
+ let mut line_editor = Reedline::create()
+ .with_completer(completer)
+ .with_history(history)
+ .with_menu(ReedlineMenu::EngineCompleter(completion_menu))
+ .with_edit_mode(edit_mode);
+ let prompt = DefaultPrompt::new(DefaultPromptSegment::Empty, DefaultPromptSegment::Empty);
+
let send_line = |line: String| -> Result<()> {
if line.is_empty() {
return Ok(());
@@ -104,8 +136,7 @@ fn run_repl(runtime: Runtime, client: Client, config: Config, role: Option<Strin
}
Ok(())
};
-
- let handle_line = |line: String| -> Result<bool> {
+ let handle_line = |line_editor: &mut Reedline, line: String| -> Result<bool> {
if line.starts_with('.') {
let (name, args) = match line.split_once(' ') {
Some((head, tail)) => (head, Some(tail.trim())),
@@ -116,7 +147,19 @@ fn run_repl(runtime: Runtime, client: Client, config: Config, role: Option<Strin
return Ok(true);
}
".help" => {
- dump(HELP);
+ dump(get_repl_help());
+ }
+ ".clear" => {
+ line_editor.clear_scrollback()?;
+ }
+ ".clear-history" => {
+ let history = Box::new(line_editor.history_mut());
+ history
+ .clear()
+ .map_err(|err| anyhow!("Failed to clear history, {err}"))?;
+ }
+ ".history" => {
+ line_editor.print_history()?;
}
".role" => match args {
Some(name) => match config.roles.iter().find(|v| v.name == name) {
@@ -137,40 +180,14 @@ fn run_repl(runtime: Runtime, client: Client, config: Config, role: Option<Strin
Ok(false)
};
if let Some(name) = role {
- handle_line(format!("role {name}"))?;
+ handle_line(&mut line_editor, format!("role {name}"))?;
}
- let mut commands = vec![".help".into(), ".exit".into(), ".role".into()];
- commands.extend(config.roles.iter().map(|v| format!(".role {}", v.name)));
- let mut completer = DefaultCompleter::with_inclusions(&['.']).set_min_word_len(2);
- completer.insert(commands.clone());
- let completer = Box::new(completer);
- let completion_menu = Box::new(ColumnarMenu::default().with_name("completion_menu"));
- let mut keybindings = default_emacs_keybindings();
- keybindings.add_binding(
- KeyModifiers::NONE,
- KeyCode::Tab,
- ReedlineEvent::UntilFound(vec![
- ReedlineEvent::Menu("completion_menu".to_string()),
- ReedlineEvent::MenuNext,
- ]),
- );
- let history = Box::new(
- FileBackedHistory::with_file(1000, get_history_path()?)
- .map_err(|err| anyhow!("Failed to setup history file, {err}"))?,
- );
- let edit_mode = Box::new(Emacs::new(keybindings));
- let mut line_editor = Reedline::create()
- .with_completer(completer)
- .with_history(history)
- .with_menu(ReedlineMenu::EngineCompleter(completion_menu))
- .with_edit_mode(edit_mode);
- let prompt = DefaultPrompt::new(DefaultPromptSegment::Empty, DefaultPromptSegment::Empty);
loop {
let sig = line_editor.read_line(&prompt);
match sig {
Ok(Signal::Success(line)) => {
- let quit = handle_line(line)?;
+ let quit = handle_line(&mut line_editor, line)?;
if quit {
break;
}
@@ -309,3 +326,17 @@ fn get_history_path() -> Result<PathBuf> {
let config_dir = dirs::home_dir().ok_or_else(|| anyhow!("No home dir"))?;
Ok(config_dir.join(format!(".{}_history", env!("CARGO_CRATE_NAME"))))
}
+
+fn print_repl_title() {
+ println!("Welcome to aichat {}", env!("CARGO_PKG_VERSION"));
+ println!("Type \".help\" for more information.");
+}
+
+fn get_repl_help() -> String {
+ let head = REPL_COMMANDS
+ .iter()
+ .map(|(name, desc)| format!("{name:<15} {desc}"))
+ .collect::<Vec<String>>()
+ .join("\n");
+ format!("{head}\n\nPress Ctrl+C/Ctrl+D to exit the REPL")
+}