pub mod model;
pub mod utils;
use crate::config::model::Model;
use crate::encryption::{encrypt, nonce};
use clap::Parser;
use colored::*;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Parser, Debug)]
#[command(
name = "pgpt",
about = "Ask ChatGPT anything directly from the terminal with pretty markdown rendering!",
version = env!("CARGO_PKG_VERSION"),
author = "Ammar Ahmed <ammar.ahmed2203@gmail.com>"
)]
pub struct CLI {
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Subcommand, Debug)]
pub enum Commands {
Query {
#[arg(required = false)]
query: Vec<String>,
#[arg(long)]
cost: bool,
#[arg(long, short, value_enum)]
model: Option<Model>,
#[arg(long, short)]
context: Option<usize>,
#[arg(long, short)]
show_context: bool,
},
Config {
#[command(subcommand)]
config_commands: ConfigCommands,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum ConfigCommands {
Set {
#[command(subcommand)]
config_setters: ConfigSetters,
},
Show {
#[command(subcommand)]
config_settings: ConfigSettings,
},
Clear {
#[command(subcommand)]
config_removers: ConfigRemovers,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum ConfigSetters {
Model {
#[arg(value_enum)]
value: Model,
},
APIKey { value: String },
CacheLength { value: usize },
Context { value: usize },
}
impl ConfigSetters {
pub fn set(&self) -> anyhow::Result<()> {
let mut config = utils::load_config_file()?;
match self {
Self::APIKey { value } => match value.trim() {
value if !value.is_empty() => {
utils::save_api_key(&value)?;
return Ok(());
}
e => return Err(anyhow::anyhow!("Received empty API key - {}", e)),
},
Self::CacheLength { value } => {
println!(
"Setting {} to {}",
"cache-length".cyan(),
value.to_string().cyan()
);
config.cache_length = *value;
}
Self::Model { value } => {
let str_model = value.to_string();
println!("Setting {} to {}", "model".cyan(), str_model.cyan());
config.model = str_model;
}
Self::Context { value } => {
println!(
"Setting {} to {}",
"context".cyan(),
value.to_string().cyan()
);
config.context = *value;
}
};
utils::save_config_file(&config)?;
Ok(())
}
}
#[derive(clap::Subcommand, Debug)]
pub enum ConfigRemovers {
APIKey,
Cache,
}
impl ConfigRemovers {
pub fn clear(&self) -> anyhow::Result<()> {
match self {
Self::APIKey => utils::clear_api_key(),
Self::Cache => utils::clear_cache(),
}
}
}
#[derive(clap::Subcommand, Debug)]
pub enum ConfigSettings {
Model,
APIKey,
CacheLength,
Cache,
Context,
All,
}
impl ConfigSettings {
pub fn show(&self) -> anyhow::Result<()> {
let config = utils::load_config_file().unwrap_or_else(|_| {
utils::register_config_file().unwrap_or_else(|e| panic!("{:#?}", e))
});
let api_key = utils::load_api_key()
.unwrap_or_else(|_| utils::register_api_key().unwrap_or_else(|e| panic!("{:#?}", e)));
let encrypted = encrypt(api_key, utils::encryption_password(), nonce()?)?;
let enc_str = String::from_utf8_lossy(&encrypted);
match self {
Self::Model => {
println!("{}: {}", "Model".cyan(), config.model);
}
Self::APIKey => {
println!("{}: {}", "API Key (encrypted)".cyan(), enc_str);
}
Self::Cache => {
let cache = utils::load_cache()?;
println!("{}:", "Cache".cyan());
for (i, value) in cache.iter().enumerate() {
println!("{}", format!("Cached {}/{}", i + 1, cache.len()).cyan());
println!("{}: {}", "You said".yellow(), value.prompt);
println!("{}:\n{}", "GPT said".magenta(), value.response);
println!("")
}
}
Self::CacheLength => {
println!("{}: {}", "Cache Length".cyan(), config.cache_length);
}
Self::Context => {
println!("{}: {}", "Context".cyan(), config.context);
}
Self::All => {
println!("{}: {}", "Model".cyan(), config.model);
println!("{}: {}", "API Key (encrypted)".cyan(), enc_str);
println!("{}: {}", "Cache Length".cyan(), config.cache_length);
println!("{}: {}", "Context".cyan(), config.context);
println!(
"To display cache, run `{}`",
"pgpt config show cache".cyan()
);
}
};
Ok(())
}
}
pub struct QueryArgs {
pub model: Option<Model>,
pub query: String,
pub cost: bool,
pub context: Option<usize>,
pub show_context: bool,
}
pub enum ParsedArgs {
Query { args: Arc<QueryArgs> },
Config { config: ConfigCommands },
}
#[derive(Debug)]
pub struct Config {
pub api_key: String,
pub model: Model,
pub cache_length: usize,
pub context: usize,
}
impl Config {
pub fn load_config() -> anyhow::Result<Arc<Self>> {
let api_key = utils::load_api_key()
.unwrap_or_else(|_| utils::register_api_key().unwrap_or_else(|e| panic!("{:#?}", e)));
let config_json = utils::load_config_file().unwrap_or_else(|_| {
utils::register_config_file().unwrap_or_else(|e| panic!("{:#?}", e))
});
let model = Model::from_str(&config_json.model).map_err(|e| anyhow::anyhow!("{:#?}", e))?;
utils::register_cache()?;
let config = Self {
api_key,
model,
cache_length: config_json.cache_length,
context: config_json.context,
};
Ok(Arc::new(config))
}
pub fn parse_args() -> ParsedArgs {
let cli = CLI::parse();
match cli.command {
Commands::Query {
query,
cost,
model,
context,
show_context,
} => {
let query = query.join(" ");
let args = QueryArgs {
query,
model,
cost,
context,
show_context,
};
ParsedArgs::Query {
args: Arc::new(args),
}
}
Commands::Config { config_commands } => ParsedArgs::Config {
config: config_commands,
},
}
}
pub fn handle_config(config_commands: &ConfigCommands) -> anyhow::Result<()> {
match config_commands {
ConfigCommands::Show { config_settings } => config_settings.show(),
ConfigCommands::Set { config_setters } => config_setters.set(),
ConfigCommands::Clear { config_removers } => config_removers.clear(),
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ConfigJSON {
pub model: String,
pub cache_length: usize,
pub context: usize,
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CacheValue {
pub prompt: String,
pub response: String,
}