diff --git a/Cargo.toml b/Cargo.toml index cf9ef45..ae88ccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ serde_json = "1.0" dirs = "5.0" spinners = "4.1.1" cli-clipboard = "0.4.0" +sqlite = "0.33.0" +tabled = "0.15.0" +terminal_size = "0.3.0" diff --git a/src/commands/history/mod.rs b/src/commands/history/mod.rs new file mode 100644 index 0000000..1a9f006 --- /dev/null +++ b/src/commands/history/mod.rs @@ -0,0 +1,104 @@ +use clap::Args; +use sqlite::{Connection, State, Value}; +use tabled::{ + settings::{peaker::PriorityMax, Width}, + Table, Tabled, +}; + +#[derive(Debug, Args)] +#[clap(name = "history", about = "Display the previous search history")] +pub struct History { + #[clap(short, long, default_value = "10")] + limit: i64, + + #[clap(short, long, default_value = "0")] + offset: i64, + + #[clap(short, long)] + query: Option, +} + +#[derive(Tabled)] +pub struct HistoryRow { + id: i64, + input: String, + output: String, + created_at: String, +} + +pub async fn connect_history() -> Connection { + let home_dir = dirs::home_dir().unwrap(); + let save_dir = home_dir.join(".cllm"); + let db_path = save_dir.join(":memory:"); + + let connection = sqlite::open(db_path).unwrap(); + + connection + .execute( + " + CREATE TABLE IF NOT EXISTS history ( + id INTEGER PRIMARY KEY, + input TEXT NOT NULL, + output TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + ", + ) + .unwrap(); + + connection +} + +pub async fn insert_history( + input: String, + output: String, +) -> Result<(), Box> { + let connection = connect_history().await; + + let query = "INSERT INTO history (input, output) VALUES (:input, :output)"; + let mut statement = connection.prepare(query).unwrap(); + statement.bind_iter::<_, (_, Value)>([(":input", input.into()), (":output", output.into())])?; + + statement.next().unwrap(); + + Ok(()) +} + +pub async fn handle_history(history: History) -> Result<(), Box> { + let limit = history.limit; + let condition = format!("%{}%", history.query.unwrap_or("".to_string())).to_string(); + + let connection = connect_history().await; + let mut rows: Vec = Vec::new(); + + let query = + "SELECT * FROM history WHERE input LIKE :query ORDER BY created_at DESC LIMIT :limit OFFSET :offset"; + let mut statement = connection.prepare(query).unwrap(); + statement.bind_iter::<_, (_, Value)>([ + (":query", condition.into()), + (":limit", limit.to_string().into()), + (":offset", history.offset.to_string().into()), + ])?; + + while let Ok(State::Row) = statement.next() { + let id: i64 = statement.read(0)?; + let input: String = statement.read(1)?; + let output: String = statement.read(2)?; + let created_at: String = statement.read(3)?; + + rows.push(HistoryRow { + id, + input, + output, + created_at, + }); + } + + let terminal_size::Width(width) = terminal_size::terminal_size().unwrap().0; + let mut table = Table::new(rows); + table.with(Width::wrap(width as usize).priority::()); + + println!("{}", table); + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f942539..06f98cd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ +mod history; mod search; mod set; @@ -7,11 +8,13 @@ use clap::Subcommand; pub enum Commands { Search(search::Search), Set(set::Set), + History(history::History), } pub async fn handle_command(command: Commands) -> Result<(), Box> { match command { Commands::Search(search) => search::handle_search(search).await, Commands::Set(set) => set::handle_set(set).await, + Commands::History(history) => history::handle_history(history).await, } } diff --git a/src/commands/search/mod.rs b/src/commands/search/mod.rs index 3f34143..0f43d00 100644 --- a/src/commands/search/mod.rs +++ b/src/commands/search/mod.rs @@ -1,3 +1,4 @@ +use super::history::insert_history; use clap::Parser; use cli_clipboard::{ClipboardContext, ClipboardProvider}; use llm_chain::{ @@ -11,6 +12,7 @@ use llm_chain::{ use llm_chain_openai::chatgpt::Model; use spinners::{Spinner, Spinners}; use std::env; + #[derive(Debug, Parser)] #[clap(name = "search", about = "Search a command from the LLM model")] pub struct Search { @@ -71,7 +73,7 @@ pub async fn handle_search(search: Search) -> Result<(), Box>()[1] @@ -81,6 +83,7 @@ pub async fn handle_search(search: Search) -> Result<(), Box