refactor: reorganize repl commands

pull/1/head
sigoden 1 year ago
parent a0d6ba7a08
commit a9128f7933

@ -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")
}

Loading…
Cancel
Save