diff options
| author | sigoden <sigoden@gmail.com> | 2023-03-02 21:19:26 +0800 |
|---|---|---|
| committer | sigoden <sigoden@gmail.com> | 2023-03-02 21:19:26 +0800 |
| commit | a9128f7933b124c50200e48be549bacc0db88b6c (patch) | |
| tree | 6c9aa1dbd222f22bf47431be781db0b5a502e9c2 /src/main.rs | |
| parent | a0d6ba7a08af4f8ca4017e5b398605bfe244c92f (diff) | |
| download | aichat-a9128f7933b124c50200e48be549bacc0db88b6c.tar.gz | |
refactor: reorganize repl commands
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 107 |
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") +} |
