From 2ab9d81f73a10172d47b3c6d4dc3c75e47895ef6 Mon Sep 17 00:00:00 2001 From: ykdy3951 Date: Fri, 1 Mar 2024 23:27:01 +0900 Subject: [PATCH 1/5] Add SQLite, tabled, and terminal_size dependencies, and implement history command --- Cargo.toml | 3 ++ src/commands/history/mod.rs | 90 +++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 3 ++ src/commands/search/mod.rs | 5 ++- 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/commands/history/mod.rs 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..e2997a3 --- /dev/null +++ b/src/commands/history/mod.rs @@ -0,0 +1,90 @@ +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, +} + +#[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 connection = connect_history().await; + let mut rows: Vec = Vec::new(); + + let query = "SELECT * FROM history ORDER BY created_at DESC LIMIT :limit"; + let mut statement = connection.prepare(query).unwrap(); + statement.bind((":limit", limit)).unwrap(); + + 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(()) +} \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f942539..7e9338f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,6 @@ mod search; mod set; +mod history; use clap::Subcommand; @@ -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..d593f53 100644 --- a/src/commands/search/mod.rs +++ b/src/commands/search/mod.rs @@ -11,6 +11,8 @@ use llm_chain::{ use llm_chain_openai::chatgpt::Model; use spinners::{Spinner, Spinners}; use std::env; +use super::history::insert_history; + #[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 Date: Sat, 2 Mar 2024 02:22:23 +0900 Subject: [PATCH 2/5] Add query parameter to history command --- src/commands/history/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/commands/history/mod.rs b/src/commands/history/mod.rs index e2997a3..0b80f88 100644 --- a/src/commands/history/mod.rs +++ b/src/commands/history/mod.rs @@ -7,6 +7,9 @@ use tabled::{settings::{peaker::PriorityMax, Width}, Table, Tabled}; pub struct History { #[clap(short, long, default_value = "10")] limit: i64, + + #[clap(short, long)] + query: Option, } #[derive(Tabled)] @@ -57,12 +60,17 @@ pub async fn insert_history(input: String, output: String) -> Result<(), Box 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 ORDER BY created_at DESC LIMIT :limit"; + let query = "SELECT * FROM history WHERE input LIKE :query ORDER BY created_at DESC LIMIT :limit"; let mut statement = connection.prepare(query).unwrap(); - statement.bind((":limit", limit)).unwrap(); + statement.bind_iter::<_, (_, Value)>([ + (":query", condition.into()), + (":limit", limit.to_string().into()) + ])?; // Bind the values to the statement while let Ok(State::Row) = statement.next() { let id: i64 = statement.read(0)?; From 1bf96976a3f58d2e623f0d1f3ba2647289fa4f60 Mon Sep 17 00:00:00 2001 From: ykdy3951 Date: Sat, 2 Mar 2024 02:28:29 +0900 Subject: [PATCH 3/5] Fix binding values in handle_history function --- src/commands/history/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/history/mod.rs b/src/commands/history/mod.rs index 0b80f88..56c0b20 100644 --- a/src/commands/history/mod.rs +++ b/src/commands/history/mod.rs @@ -70,7 +70,7 @@ pub async fn handle_history(history: History) -> Result<(), Box([ (":query", condition.into()), (":limit", limit.to_string().into()) - ])?; // Bind the values to the statement + ])?; while let Ok(State::Row) = statement.next() { let id: i64 = statement.read(0)?; From 1a988ff0eb1f8ed9127f08301978413010f1ff24 Mon Sep 17 00:00:00 2001 From: ykdy3951 Date: Sat, 2 Mar 2024 13:31:23 +0900 Subject: [PATCH 4/5] Refactor history module and fix import order --- src/commands/history/mod.rs | 44 +++++++++++++++++++------------------ src/commands/mod.rs | 2 +- src/commands/search/mod.rs | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/commands/history/mod.rs b/src/commands/history/mod.rs index 56c0b20..35ebf2c 100644 --- a/src/commands/history/mod.rs +++ b/src/commands/history/mod.rs @@ -1,6 +1,9 @@ use clap::Args; use sqlite::{Connection, State, Value}; -use tabled::{settings::{peaker::PriorityMax, Width}, Table, Tabled}; +use tabled::{ + settings::{peaker::PriorityMax, Width}, + Table, Tabled, +}; #[derive(Debug, Args)] #[clap(name = "history", about = "Display the previous search history")] @@ -21,36 +24,37 @@ pub struct HistoryRow { } 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( - " + 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(); + ", + ) + .unwrap(); connection } -pub async fn insert_history(input: String, output: String) -> Result<(), Box> { +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.bind_iter::<_, (_, Value)>([(":input", input.into()), (":output", output.into())])?; statement.next().unwrap(); @@ -58,18 +62,18 @@ pub async fn insert_history(input: String, output: String) -> Result<(), Box 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"; + let query = + "SELECT * FROM history WHERE input LIKE :query ORDER BY created_at DESC LIMIT :limit"; let mut statement = connection.prepare(query).unwrap(); statement.bind_iter::<_, (_, Value)>([ (":query", condition.into()), - (":limit", limit.to_string().into()) + (":limit", limit.to_string().into()), ])?; while let Ok(State::Row) = statement.next() { @@ -85,14 +89,12 @@ pub async fn handle_history(history: History) -> Result<(), Box() - ); + table.with(Width::wrap(width as usize).priority::()); println!("{}", table); Ok(()) -} \ No newline at end of file +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 7e9338f..06f98cd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,6 @@ +mod history; mod search; mod set; -mod history; use clap::Subcommand; diff --git a/src/commands/search/mod.rs b/src/commands/search/mod.rs index d593f53..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,7 +12,6 @@ use llm_chain::{ use llm_chain_openai::chatgpt::Model; use spinners::{Spinner, Spinners}; use std::env; -use super::history::insert_history; #[derive(Debug, Parser)] #[clap(name = "search", about = "Search a command from the LLM model")] From 9c844b4693b7fc8469b5675cb554acb29544c50f Mon Sep 17 00:00:00 2001 From: ykdy3951 Date: Sun, 3 Mar 2024 15:42:58 +0900 Subject: [PATCH 5/5] Add offset parameter to history command --- src/commands/history/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/history/mod.rs b/src/commands/history/mod.rs index 35ebf2c..1a9f006 100644 --- a/src/commands/history/mod.rs +++ b/src/commands/history/mod.rs @@ -11,6 +11,9 @@ pub struct History { #[clap(short, long, default_value = "10")] limit: i64, + #[clap(short, long, default_value = "0")] + offset: i64, + #[clap(short, long)] query: Option, } @@ -69,11 +72,12 @@ pub async fn handle_history(history: History) -> Result<(), Box = Vec::new(); let query = - "SELECT * FROM history WHERE input LIKE :query ORDER BY created_at DESC LIMIT :limit"; + "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() {