2023-11-27 06:04:50 +00:00
|
|
|
mod input;
|
2023-03-09 11:10:02 +00:00
|
|
|
mod role;
|
2023-10-28 13:39:17 +00:00
|
|
|
mod session;
|
2023-03-09 02:39:28 +00:00
|
|
|
|
2024-03-10 00:01:54 +00:00
|
|
|
pub use self::input::{Input, InputContext};
|
2023-03-09 11:10:02 +00:00
|
|
|
use self::role::Role;
|
2024-04-14 06:43:42 +00:00
|
|
|
pub use self::role::{CODE_ROLE, EXPLAIN_ROLE, SHELL_ROLE};
|
2023-10-28 13:39:17 +00:00
|
|
|
use self::session::{Session, TEMP_SESSION_NAME};
|
2023-03-09 02:39:28 +00:00
|
|
|
|
2023-10-31 08:50:08 +00:00
|
|
|
use crate::client::{
|
2024-04-28 05:28:24 +00:00
|
|
|
create_client_config, list_client_types, list_models, ClientConfig, Message, Model, SendData,
|
2023-10-31 08:50:08 +00:00
|
|
|
};
|
2023-11-02 01:53:54 +00:00
|
|
|
use crate::render::{MarkdownRender, RenderOptions};
|
2024-04-29 07:34:24 +00:00
|
|
|
use crate::utils::{
|
|
|
|
format_option_value, fuzzy_match, get_env_name, light_theme_from_colorfgbg, now, render_prompt,
|
|
|
|
set_text,
|
|
|
|
};
|
2023-03-09 00:11:38 +00:00
|
|
|
|
2023-03-09 02:39:28 +00:00
|
|
|
use anyhow::{anyhow, bail, Context, Result};
|
2023-10-28 13:39:17 +00:00
|
|
|
use inquire::{Confirm, Select, Text};
|
2023-10-30 09:28:10 +00:00
|
|
|
use is_terminal::IsTerminal;
|
2023-03-11 13:45:34 +00:00
|
|
|
use parking_lot::RwLock;
|
2023-03-09 11:10:02 +00:00
|
|
|
use serde::Deserialize;
|
2024-03-03 06:52:15 +00:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2023-03-03 01:15:58 +00:00
|
|
|
use std::{
|
2023-03-03 01:38:53 +00:00
|
|
|
env,
|
2023-10-28 13:39:17 +00:00
|
|
|
fs::{create_dir_all, read_dir, read_to_string, remove_file, File, OpenOptions},
|
2023-10-30 09:28:10 +00:00
|
|
|
io::{stdout, Write},
|
2023-03-03 01:15:58 +00:00
|
|
|
path::{Path, PathBuf},
|
2023-03-03 09:53:29 +00:00
|
|
|
process::exit,
|
2023-03-05 14:51:29 +00:00
|
|
|
sync::Arc,
|
2023-03-03 01:15:58 +00:00
|
|
|
};
|
2023-10-31 01:06:41 +00:00
|
|
|
use syntect::highlighting::ThemeSet;
|
|
|
|
|
|
|
|
/// Monokai Extended
|
|
|
|
const DARK_THEME: &[u8] = include_bytes!("../../assets/monokai-extended.theme.bin");
|
|
|
|
const LIGHT_THEME: &[u8] = include_bytes!("../../assets/monokai-extended-light.theme.bin");
|
2023-03-02 11:52:11 +00:00
|
|
|
|
2023-03-03 04:43:34 +00:00
|
|
|
const CONFIG_FILE_NAME: &str = "config.yaml";
|
|
|
|
const ROLES_FILE_NAME: &str = "roles.yaml";
|
2023-10-28 13:39:17 +00:00
|
|
|
const MESSAGES_FILE_NAME: &str = "messages.md";
|
|
|
|
const SESSIONS_DIR_NAME: &str = "sessions";
|
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
const CLIENTS_FIELD: &str = "clients";
|
|
|
|
|
2024-04-23 06:32:06 +00:00
|
|
|
const SUMMARIZE_PROMPT: &str =
|
|
|
|
"Summarize the discussion briefly in 200 words or less to use as a prompt for future context.";
|
|
|
|
const SUMMARY_PROMPT: &str = "This is a summary of the chat history as a recap: ";
|
|
|
|
const LEFT_PROMPT: &str = "{color.green}{?session {session}{?role /}}{role}{color.cyan}{?session )}{!session >}{color.reset} ";
|
|
|
|
const RIGHT_PROMPT: &str = "{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}";
|
|
|
|
|
2023-03-02 11:52:11 +00:00
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
2023-03-11 03:26:24 +00:00
|
|
|
#[serde(default)]
|
2023-03-02 11:52:11 +00:00
|
|
|
pub struct Config {
|
2023-11-02 22:52:57 +00:00
|
|
|
#[serde(rename(serialize = "model", deserialize = "model"))]
|
|
|
|
pub model_id: Option<String>,
|
2024-03-27 00:11:00 +00:00
|
|
|
pub temperature: Option<f64>,
|
2024-04-24 08:12:38 +00:00
|
|
|
pub top_p: Option<f64>,
|
2023-11-07 23:08:48 +00:00
|
|
|
pub dry_run: bool,
|
2023-03-03 01:38:53 +00:00
|
|
|
pub save: bool,
|
2024-03-27 02:02:09 +00:00
|
|
|
pub save_session: Option<bool>,
|
2023-03-05 11:48:22 +00:00
|
|
|
pub highlight: bool,
|
2023-03-11 10:53:36 +00:00
|
|
|
pub light_theme: bool,
|
2023-10-30 02:07:01 +00:00
|
|
|
pub wrap: Option<String>,
|
|
|
|
pub wrap_code: bool,
|
2023-05-04 13:22:12 +00:00
|
|
|
pub auto_copy: bool,
|
2023-10-27 13:22:24 +00:00
|
|
|
pub keybindings: Keybindings,
|
2024-04-18 10:22:15 +00:00
|
|
|
pub prelude: Option<String>,
|
|
|
|
pub buffer_editor: Option<String>,
|
2024-03-04 03:08:59 +00:00
|
|
|
pub compress_threshold: usize,
|
2024-04-23 06:32:06 +00:00
|
|
|
pub summarize_prompt: Option<String>,
|
|
|
|
pub summary_prompt: Option<String>,
|
|
|
|
pub left_prompt: Option<String>,
|
|
|
|
pub right_prompt: Option<String>,
|
2023-10-26 08:42:54 +00:00
|
|
|
pub clients: Vec<ClientConfig>,
|
2023-03-09 07:30:39 +00:00
|
|
|
#[serde(skip)]
|
2023-03-02 11:52:11 +00:00
|
|
|
pub roles: Vec<Role>,
|
2023-03-09 07:30:39 +00:00
|
|
|
#[serde(skip)]
|
2023-03-05 14:51:29 +00:00
|
|
|
pub role: Option<Role>,
|
2023-03-09 07:30:39 +00:00
|
|
|
#[serde(skip)]
|
2023-10-28 13:39:17 +00:00
|
|
|
pub session: Option<Session>,
|
2023-03-16 09:02:09 +00:00
|
|
|
#[serde(skip)]
|
2023-11-02 22:52:57 +00:00
|
|
|
pub model: Model,
|
2023-10-28 13:39:17 +00:00
|
|
|
#[serde(skip)]
|
2024-04-23 23:16:56 +00:00
|
|
|
pub working_mode: WorkingMode,
|
2024-03-27 02:25:42 +00:00
|
|
|
#[serde(skip)]
|
2024-04-23 23:16:56 +00:00
|
|
|
pub last_message: Option<(Input, String)>,
|
2023-03-02 11:52:11 +00:00
|
|
|
}
|
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2023-11-02 22:52:57 +00:00
|
|
|
model_id: None,
|
2024-03-27 00:11:00 +00:00
|
|
|
temperature: None,
|
2024-04-24 08:12:38 +00:00
|
|
|
top_p: None,
|
2024-04-28 05:28:24 +00:00
|
|
|
save: false,
|
2024-03-27 02:02:09 +00:00
|
|
|
save_session: None,
|
2023-03-11 03:26:24 +00:00
|
|
|
highlight: true,
|
|
|
|
dry_run: false,
|
2023-03-11 10:53:36 +00:00
|
|
|
light_theme: false,
|
2023-10-30 02:07:01 +00:00
|
|
|
wrap: None,
|
|
|
|
wrap_code: false,
|
2023-05-04 13:22:12 +00:00
|
|
|
auto_copy: false,
|
2023-10-27 13:22:24 +00:00
|
|
|
keybindings: Default::default(),
|
2024-04-18 10:22:15 +00:00
|
|
|
prelude: None,
|
|
|
|
buffer_editor: None,
|
2024-03-04 03:08:59 +00:00
|
|
|
compress_threshold: 2000,
|
2024-04-23 06:32:06 +00:00
|
|
|
summarize_prompt: None,
|
|
|
|
summary_prompt: None,
|
|
|
|
left_prompt: None,
|
|
|
|
right_prompt: None,
|
|
|
|
clients: vec![],
|
2023-10-28 13:39:17 +00:00
|
|
|
roles: vec![],
|
2023-03-11 03:26:24 +00:00
|
|
|
role: None,
|
2023-10-28 13:39:17 +00:00
|
|
|
session: None,
|
2023-11-02 22:52:57 +00:00
|
|
|
model: Default::default(),
|
2024-04-23 23:16:56 +00:00
|
|
|
working_mode: WorkingMode::Command,
|
2023-10-28 13:39:17 +00:00
|
|
|
last_message: None,
|
2023-03-11 03:26:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-02 02:45:11 +00:00
|
|
|
pub type GlobalConfig = Arc<RwLock<Config>>;
|
2023-03-05 14:51:29 +00:00
|
|
|
|
2023-03-02 11:52:11 +00:00
|
|
|
impl Config {
|
2024-04-23 23:16:56 +00:00
|
|
|
pub fn init(working_mode: WorkingMode) -> Result<Self> {
|
2023-03-11 03:26:24 +00:00
|
|
|
let config_path = Self::config_file()?;
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2024-04-28 05:28:24 +00:00
|
|
|
let client_type = env::var(get_env_name("client_type")).ok();
|
|
|
|
if working_mode != WorkingMode::Command && client_type.is_none() && !config_path.exists() {
|
2023-03-03 09:53:29 +00:00
|
|
|
create_config_file(&config_path)?;
|
|
|
|
}
|
2024-04-28 05:28:24 +00:00
|
|
|
let mut config = if client_type.is_some() {
|
|
|
|
Self::load_config_env(&client_type.unwrap())?
|
2023-03-11 03:26:24 +00:00
|
|
|
} else {
|
2024-04-28 05:28:24 +00:00
|
|
|
Self::load_config_file(&config_path)?
|
2023-03-11 03:26:24 +00:00
|
|
|
};
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2023-10-30 02:07:01 +00:00
|
|
|
if let Some(wrap) = config.wrap.clone() {
|
|
|
|
config.set_wrap(&wrap)?;
|
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
|
2024-04-23 23:16:56 +00:00
|
|
|
config.working_mode = working_mode;
|
2023-03-03 01:38:53 +00:00
|
|
|
config.load_roles()?;
|
2023-11-01 02:28:54 +00:00
|
|
|
|
2023-11-02 22:52:57 +00:00
|
|
|
config.setup_model()?;
|
2023-11-01 02:28:54 +00:00
|
|
|
config.setup_highlight();
|
|
|
|
config.setup_light_theme()?;
|
2023-03-09 15:30:12 +00:00
|
|
|
|
2023-03-02 11:52:11 +00:00
|
|
|
Ok(config)
|
|
|
|
}
|
2023-03-03 04:43:34 +00:00
|
|
|
|
2024-04-18 10:22:15 +00:00
|
|
|
pub fn apply_prelude(&mut self) -> Result<()> {
|
|
|
|
let prelude = self.prelude.clone().unwrap_or_default();
|
|
|
|
if prelude.is_empty() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2023-11-07 11:14:58 +00:00
|
|
|
let err_msg = || format!("Invalid prelude '{}", prelude);
|
|
|
|
match prelude.split_once(':') {
|
|
|
|
Some(("role", name)) => {
|
|
|
|
if self.role.is_none() && self.session.is_none() {
|
|
|
|
self.set_role(name).with_context(err_msg)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(("session", name)) => {
|
|
|
|
if self.session.is_none() {
|
|
|
|
self.start_session(Some(name)).with_context(err_msg)?;
|
|
|
|
}
|
|
|
|
}
|
2024-04-18 10:22:15 +00:00
|
|
|
_ => {
|
2023-11-07 11:14:58 +00:00
|
|
|
bail!("{}", err_msg())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-04-18 10:22:15 +00:00
|
|
|
pub fn buffer_editor(&self) -> Option<String> {
|
|
|
|
self.buffer_editor
|
|
|
|
.clone()
|
|
|
|
.or_else(|| env::var("VISUAL").ok().or_else(|| env::var("EDITOR").ok()))
|
|
|
|
}
|
|
|
|
|
2023-10-30 11:26:06 +00:00
|
|
|
pub fn retrieve_role(&self, name: &str) -> Result<Role> {
|
|
|
|
self.roles
|
|
|
|
.iter()
|
|
|
|
.find(|v| v.match_name(name))
|
|
|
|
.map(|v| {
|
|
|
|
let mut role = v.clone();
|
|
|
|
role.complete_prompt_args(name);
|
|
|
|
role
|
|
|
|
})
|
2024-04-14 06:43:42 +00:00
|
|
|
.or_else(|| Role::find_system_role(name))
|
2023-10-30 11:26:06 +00:00
|
|
|
.ok_or_else(|| anyhow!("Unknown role `{name}`"))
|
2023-03-03 09:53:29 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 21:50:30 +00:00
|
|
|
pub fn config_dir() -> Result<PathBuf> {
|
2023-03-11 03:26:24 +00:00
|
|
|
let env_name = get_env_name("config_dir");
|
2023-07-10 03:07:09 +00:00
|
|
|
let path = if let Some(v) = env::var_os(env_name) {
|
|
|
|
PathBuf::from(v)
|
|
|
|
} else {
|
|
|
|
let mut dir = dirs::config_dir().ok_or_else(|| anyhow!("Not found config dir"))?;
|
|
|
|
dir.push(env!("CARGO_CRATE_NAME"));
|
|
|
|
dir
|
2023-03-03 01:38:53 +00:00
|
|
|
};
|
2023-03-04 21:50:30 +00:00
|
|
|
Ok(path)
|
|
|
|
}
|
|
|
|
|
2023-10-28 13:39:17 +00:00
|
|
|
pub fn local_path(name: &str) -> Result<PathBuf> {
|
2023-03-04 21:50:30 +00:00
|
|
|
let mut path = Self::config_dir()?;
|
2023-03-03 01:38:53 +00:00
|
|
|
path.push(name);
|
|
|
|
Ok(path)
|
|
|
|
}
|
2023-03-03 04:43:34 +00:00
|
|
|
|
2023-11-27 06:04:50 +00:00
|
|
|
pub fn save_message(&mut self, input: Input, output: &str) -> Result<()> {
|
|
|
|
self.last_message = Some((input.clone(), output.to_string()));
|
2023-10-28 13:39:17 +00:00
|
|
|
|
2023-10-30 02:07:01 +00:00
|
|
|
if self.dry_run {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2024-03-10 00:01:54 +00:00
|
|
|
if let Some(session) = input.session_mut(&mut self.session) {
|
2023-11-27 06:04:50 +00:00
|
|
|
session.add_message(&input, output)?;
|
2023-10-28 13:39:17 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2023-03-03 09:53:29 +00:00
|
|
|
if !self.save {
|
2023-03-08 03:27:51 +00:00
|
|
|
return Ok(());
|
2023-03-03 09:53:29 +00:00
|
|
|
}
|
2023-03-08 03:27:51 +00:00
|
|
|
let mut file = self.open_message_file()?;
|
|
|
|
if output.is_empty() || !self.save {
|
2023-03-05 22:45:00 +00:00
|
|
|
return Ok(());
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
2023-03-05 22:45:00 +00:00
|
|
|
let timestamp = now();
|
2024-02-22 03:39:22 +00:00
|
|
|
let summary = input.summary();
|
2023-11-27 06:04:50 +00:00
|
|
|
let input_markdown = input.render();
|
2024-03-10 00:01:54 +00:00
|
|
|
let output = match input.role() {
|
2023-03-05 22:45:00 +00:00
|
|
|
None => {
|
2024-02-22 03:39:22 +00:00
|
|
|
format!("# CHAT: {summary} [{timestamp}]\n{input_markdown}\n--------\n{output}\n--------\n\n",)
|
2023-03-05 22:45:00 +00:00
|
|
|
}
|
|
|
|
Some(v) => {
|
2023-07-10 03:07:09 +00:00
|
|
|
format!(
|
2024-02-22 03:39:22 +00:00
|
|
|
"# CHAT: {summary} [{timestamp}] ({})\n{input_markdown}\n--------\n{output}\n--------\n\n",
|
2023-07-10 03:07:09 +00:00
|
|
|
v.name,
|
|
|
|
)
|
2023-03-05 22:45:00 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
file.write_all(output.as_bytes())
|
|
|
|
.with_context(|| "Failed to save message")
|
2023-03-03 09:53:29 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 14:02:29 +00:00
|
|
|
pub fn maybe_copy(&self, text: &str) {
|
|
|
|
if self.auto_copy {
|
|
|
|
let _ = set_text(text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 04:43:34 +00:00
|
|
|
pub fn config_file() -> Result<PathBuf> {
|
2023-10-28 13:39:17 +00:00
|
|
|
Self::local_path(CONFIG_FILE_NAME)
|
2023-03-03 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn roles_file() -> Result<PathBuf> {
|
2023-03-11 03:26:24 +00:00
|
|
|
let env_name = get_env_name("roles_file");
|
2023-07-10 03:07:09 +00:00
|
|
|
env::var(env_name).map_or_else(
|
2023-10-28 13:39:17 +00:00
|
|
|
|_| Self::local_path(ROLES_FILE_NAME),
|
2023-07-10 03:07:09 +00:00
|
|
|
|value| Ok(PathBuf::from(value)),
|
|
|
|
)
|
2023-03-03 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 21:50:30 +00:00
|
|
|
pub fn messages_file() -> Result<PathBuf> {
|
2023-10-28 13:39:17 +00:00
|
|
|
Self::local_path(MESSAGES_FILE_NAME)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn sessions_dir() -> Result<PathBuf> {
|
|
|
|
Self::local_path(SESSIONS_DIR_NAME)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn session_file(name: &str) -> Result<PathBuf> {
|
|
|
|
let mut path = Self::sessions_dir()?;
|
|
|
|
path.push(&format!("{name}.yaml"));
|
|
|
|
Ok(path)
|
2023-03-03 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
2024-04-20 01:21:52 +00:00
|
|
|
pub fn set_prompt(&mut self, prompt: &str) -> Result<()> {
|
|
|
|
let role = Role::temp(prompt);
|
|
|
|
self.set_role_obj(role)
|
|
|
|
}
|
|
|
|
|
2023-10-30 11:26:06 +00:00
|
|
|
pub fn set_role(&mut self, name: &str) -> Result<()> {
|
|
|
|
let role = self.retrieve_role(name)?;
|
2024-02-23 05:15:18 +00:00
|
|
|
self.set_role_obj(role)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_role_obj(&mut self, role: Role) -> Result<()> {
|
2023-10-30 11:26:06 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-03-17 00:23:47 +00:00
|
|
|
session.guard_empty()?;
|
|
|
|
session.set_temperature(role.temperature);
|
2024-04-24 08:12:38 +00:00
|
|
|
session.set_top_p(role.top_p);
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
2023-10-30 11:26:06 +00:00
|
|
|
self.role = Some(role);
|
|
|
|
Ok(())
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
|
|
|
|
2023-03-09 23:13:22 +00:00
|
|
|
pub fn clear_role(&mut self) -> Result<()> {
|
|
|
|
self.role = None;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-11-27 07:39:55 +00:00
|
|
|
pub fn get_state(&self) -> State {
|
|
|
|
if let Some(session) = &self.session {
|
|
|
|
if session.is_empty() {
|
2024-03-17 00:23:47 +00:00
|
|
|
if self.role.is_some() {
|
2023-11-27 07:39:55 +00:00
|
|
|
State::EmptySessionWithRole
|
|
|
|
} else {
|
|
|
|
State::EmptySession
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
State::Session
|
|
|
|
}
|
|
|
|
} else if self.role.is_some() {
|
|
|
|
State::Role
|
|
|
|
} else {
|
|
|
|
State::Normal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-04 03:08:59 +00:00
|
|
|
pub fn set_temperature(&mut self, value: Option<f64>) {
|
2023-10-30 04:58:11 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
2023-11-01 14:15:55 +00:00
|
|
|
session.set_temperature(value);
|
2024-03-27 00:11:00 +00:00
|
|
|
} else if let Some(role) = self.role.as_mut() {
|
|
|
|
role.set_temperature(value);
|
|
|
|
} else {
|
|
|
|
self.temperature = value;
|
2023-10-30 04:58:11 +00:00
|
|
|
}
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
|
|
|
|
2024-04-24 08:12:38 +00:00
|
|
|
pub fn set_top_p(&mut self, value: Option<f64>) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.set_top_p(value);
|
|
|
|
} else if let Some(role) = self.role.as_mut() {
|
|
|
|
role.set_top_p(value);
|
|
|
|
} else {
|
|
|
|
self.top_p = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-27 02:02:09 +00:00
|
|
|
pub fn set_save_session(&mut self, value: Option<bool>) {
|
2024-03-26 23:33:21 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.set_save_session(value);
|
|
|
|
} else {
|
|
|
|
self.save_session = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-27 00:23:24 +00:00
|
|
|
pub fn set_compress_threshold(&mut self, value: Option<usize>) {
|
2024-03-04 03:08:59 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.set_compress_threshold(value);
|
2024-03-27 00:23:24 +00:00
|
|
|
} else {
|
|
|
|
self.compress_threshold = value.unwrap_or_default();
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
2023-03-08 11:43:11 +00:00
|
|
|
}
|
|
|
|
|
2023-11-27 06:04:50 +00:00
|
|
|
pub fn echo_messages(&self, input: &Input) -> String {
|
2024-03-10 00:01:54 +00:00
|
|
|
if let Some(session) = input.session(&self.session) {
|
2023-11-27 06:04:50 +00:00
|
|
|
session.echo_messages(input)
|
2024-03-10 00:01:54 +00:00
|
|
|
} else if let Some(role) = input.role() {
|
2023-11-27 06:04:50 +00:00
|
|
|
role.echo_messages(input)
|
2023-03-09 02:39:28 +00:00
|
|
|
} else {
|
2023-11-27 06:04:50 +00:00
|
|
|
input.render()
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-27 06:04:50 +00:00
|
|
|
pub fn build_messages(&self, input: &Input) -> Result<Vec<Message>> {
|
2024-03-10 00:01:54 +00:00
|
|
|
let messages = if let Some(session) = input.session(&self.session) {
|
2023-11-27 06:04:50 +00:00
|
|
|
session.build_emssages(input)
|
2024-03-10 00:01:54 +00:00
|
|
|
} else if let Some(role) = input.role() {
|
2023-11-27 06:04:50 +00:00
|
|
|
role.build_messages(input)
|
2023-03-09 02:39:28 +00:00
|
|
|
} else {
|
2023-11-27 06:04:50 +00:00
|
|
|
let message = Message::new(input);
|
2023-03-09 11:10:02 +00:00
|
|
|
vec![message]
|
2023-03-09 07:30:39 +00:00
|
|
|
};
|
2023-03-09 11:10:02 +00:00
|
|
|
Ok(messages)
|
2023-03-08 09:13:11 +00:00
|
|
|
}
|
|
|
|
|
2023-10-30 02:07:01 +00:00
|
|
|
pub fn set_wrap(&mut self, value: &str) -> Result<()> {
|
|
|
|
if value == "no" {
|
|
|
|
self.wrap = None;
|
|
|
|
} else if value == "auto" {
|
|
|
|
self.wrap = Some(value.into());
|
|
|
|
} else {
|
|
|
|
value
|
|
|
|
.parse::<u16>()
|
|
|
|
.map_err(|_| anyhow!("Invalid wrap value"))?;
|
|
|
|
self.wrap = Some(value.into())
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-10-26 08:42:54 +00:00
|
|
|
pub fn set_model(&mut self, value: &str) -> Result<()> {
|
2023-11-02 07:33:22 +00:00
|
|
|
let models = list_models(self);
|
2023-11-07 03:54:56 +00:00
|
|
|
let model = Model::find(&models, value);
|
2023-11-02 22:52:57 +00:00
|
|
|
match model {
|
2024-04-28 06:15:10 +00:00
|
|
|
None => bail!("No model '{}'", value),
|
2023-11-02 22:52:57 +00:00
|
|
|
Some(model) => {
|
2023-10-28 13:39:17 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
2023-11-02 22:52:57 +00:00
|
|
|
session.set_model(model.clone())?;
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2023-11-02 22:52:57 +00:00
|
|
|
self.model = model;
|
2023-10-28 13:39:17 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2023-03-16 09:02:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-27 03:20:00 +00:00
|
|
|
pub fn system_info(&self) -> Result<String> {
|
2023-12-22 22:35:23 +00:00
|
|
|
let display_path = |path: &Path| path.display().to_string();
|
2023-10-30 02:07:01 +00:00
|
|
|
let wrap = self
|
|
|
|
.wrap
|
|
|
|
.clone()
|
|
|
|
.map_or_else(|| String::from("no"), |v| v.to_string());
|
2023-03-05 14:51:29 +00:00
|
|
|
let items = vec![
|
2023-11-02 22:52:57 +00:00
|
|
|
("model", self.model.id()),
|
2024-04-30 00:57:22 +00:00
|
|
|
(
|
|
|
|
"max_output_tokens",
|
|
|
|
self.model
|
|
|
|
.max_output_tokens
|
|
|
|
.map(|v| format!("{v} (current model)"))
|
|
|
|
.unwrap_or_else(|| "-".into()),
|
|
|
|
),
|
2024-04-29 07:34:24 +00:00
|
|
|
("temperature", format_option_value(&self.temperature)),
|
|
|
|
("top_p", format_option_value(&self.top_p)),
|
2023-11-02 01:53:54 +00:00
|
|
|
("dry_run", self.dry_run.to_string()),
|
2023-03-05 14:51:29 +00:00
|
|
|
("save", self.save.to_string()),
|
2024-04-29 07:34:24 +00:00
|
|
|
("save_session", format_option_value(&self.save_session)),
|
2023-03-05 14:51:29 +00:00
|
|
|
("highlight", self.highlight.to_string()),
|
2023-11-07 23:08:48 +00:00
|
|
|
("light_theme", self.light_theme.to_string()),
|
2023-10-30 02:07:01 +00:00
|
|
|
("wrap", wrap),
|
|
|
|
("wrap_code", self.wrap_code.to_string()),
|
2023-11-07 23:08:48 +00:00
|
|
|
("auto_copy", self.auto_copy.to_string()),
|
2023-10-27 13:22:24 +00:00
|
|
|
("keybindings", self.keybindings.stringify().into()),
|
2024-04-29 07:34:24 +00:00
|
|
|
("prelude", format_option_value(&self.prelude)),
|
2024-03-04 03:08:59 +00:00
|
|
|
("compress_threshold", self.compress_threshold.to_string()),
|
2023-11-07 15:07:42 +00:00
|
|
|
("config_file", display_path(&Self::config_file()?)),
|
|
|
|
("roles_file", display_path(&Self::roles_file()?)),
|
|
|
|
("messages_file", display_path(&Self::messages_file()?)),
|
|
|
|
("sessions_dir", display_path(&Self::sessions_dir()?)),
|
2023-03-05 14:51:29 +00:00
|
|
|
];
|
2023-11-02 01:53:54 +00:00
|
|
|
let output = items
|
|
|
|
.iter()
|
|
|
|
.map(|(name, value)| format!("{name:<20}{value}"))
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join("\n");
|
2023-03-05 14:51:29 +00:00
|
|
|
Ok(output)
|
|
|
|
}
|
|
|
|
|
2023-11-02 01:53:54 +00:00
|
|
|
pub fn role_info(&self) -> Result<String> {
|
|
|
|
if let Some(role) = &self.role {
|
2024-03-04 03:08:59 +00:00
|
|
|
role.export()
|
2023-11-02 01:53:54 +00:00
|
|
|
} else {
|
|
|
|
bail!("No role")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn session_info(&self) -> Result<String> {
|
|
|
|
if let Some(session) = &self.session {
|
|
|
|
let render_options = self.get_render_options()?;
|
|
|
|
let mut markdown_render = MarkdownRender::init(render_options)?;
|
2024-03-04 03:08:59 +00:00
|
|
|
session.info(&mut markdown_render)
|
2023-11-02 01:53:54 +00:00
|
|
|
} else {
|
|
|
|
bail!("No session")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn info(&self) -> Result<String> {
|
|
|
|
if let Some(session) = &self.session {
|
|
|
|
session.export()
|
|
|
|
} else if let Some(role) = &self.role {
|
2024-03-04 03:08:59 +00:00
|
|
|
role.export()
|
2023-11-02 01:53:54 +00:00
|
|
|
} else {
|
2024-03-27 03:20:00 +00:00
|
|
|
self.system_info()
|
2023-11-02 01:53:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn last_reply(&self) -> &str {
|
|
|
|
self.last_message
|
|
|
|
.as_ref()
|
|
|
|
.map(|(_, reply)| reply.as_str())
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
2024-04-29 07:34:24 +00:00
|
|
|
pub fn repl_complete(&self, cmd: &str, args: &[&str]) -> Vec<(String, String)> {
|
2023-11-07 14:18:59 +00:00
|
|
|
let (values, filter) = if args.len() == 1 {
|
|
|
|
let values = match cmd {
|
2024-04-29 07:34:24 +00:00
|
|
|
".role" => self
|
|
|
|
.roles
|
|
|
|
.iter()
|
|
|
|
.map(|v| (v.name.clone(), String::new()))
|
|
|
|
.collect(),
|
|
|
|
".model" => list_models(self)
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| (v.id(), v.description()))
|
|
|
|
.collect(),
|
|
|
|
".session" => self
|
|
|
|
.list_sessions()
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| (v.clone(), String::new()))
|
|
|
|
.collect(),
|
2023-11-07 14:18:59 +00:00
|
|
|
".set" => vec![
|
2024-04-30 00:57:22 +00:00
|
|
|
"max_output_tokens",
|
|
|
|
"temperature",
|
|
|
|
"top_p",
|
2024-03-04 03:08:59 +00:00
|
|
|
"compress_threshold",
|
2024-04-30 00:57:22 +00:00
|
|
|
"save",
|
|
|
|
"save_session",
|
|
|
|
"highlight",
|
|
|
|
"dry_run",
|
|
|
|
"auto_copy",
|
2023-11-02 07:33:22 +00:00
|
|
|
]
|
2023-11-07 14:18:59 +00:00
|
|
|
.into_iter()
|
2024-04-30 00:57:22 +00:00
|
|
|
.map(|v| (format!("{v} "), String::new()))
|
2023-11-07 14:18:59 +00:00
|
|
|
.collect(),
|
|
|
|
_ => vec![],
|
|
|
|
};
|
|
|
|
(values, args[0])
|
|
|
|
} else if args.len() == 2 {
|
|
|
|
let values = match args[0] {
|
2024-04-30 00:57:22 +00:00
|
|
|
"max_output_tokens" => match self.model.show_max_output_tokens() {
|
|
|
|
Some(v) => vec![v.to_string()],
|
|
|
|
None => vec![],
|
|
|
|
},
|
2024-03-27 02:02:09 +00:00
|
|
|
"save" => complete_bool(self.save),
|
2024-03-26 23:33:21 +00:00
|
|
|
"save_session" => {
|
2024-03-27 02:02:09 +00:00
|
|
|
let save_session = if let Some(session) = &self.session {
|
|
|
|
session.save_session()
|
2024-03-26 23:33:21 +00:00
|
|
|
} else {
|
2024-03-27 02:02:09 +00:00
|
|
|
self.save_session
|
|
|
|
};
|
|
|
|
complete_option_bool(save_session)
|
2024-03-26 23:33:21 +00:00
|
|
|
}
|
2024-03-27 02:02:09 +00:00
|
|
|
"highlight" => complete_bool(self.highlight),
|
|
|
|
"dry_run" => complete_bool(self.dry_run),
|
|
|
|
"auto_copy" => complete_bool(self.auto_copy),
|
2023-11-07 14:18:59 +00:00
|
|
|
_ => vec![],
|
|
|
|
};
|
2024-04-29 07:34:24 +00:00
|
|
|
(
|
|
|
|
values.into_iter().map(|v| (v, String::new())).collect(),
|
|
|
|
args[1],
|
|
|
|
)
|
2023-11-07 14:18:59 +00:00
|
|
|
} else {
|
|
|
|
return vec![];
|
2023-11-02 07:33:22 +00:00
|
|
|
};
|
2023-11-27 09:22:16 +00:00
|
|
|
values
|
2023-11-02 07:33:22 +00:00
|
|
|
.into_iter()
|
2024-04-29 07:34:24 +00:00
|
|
|
.filter(|(value, _)| fuzzy_match(value, filter))
|
2023-11-27 09:22:16 +00:00
|
|
|
.collect()
|
2023-03-05 22:45:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-09 02:39:28 +00:00
|
|
|
pub fn update(&mut self, data: &str) -> Result<()> {
|
2023-03-05 14:51:29 +00:00
|
|
|
let parts: Vec<&str> = data.split_whitespace().collect();
|
|
|
|
if parts.len() != 2 {
|
2023-03-09 02:39:28 +00:00
|
|
|
bail!("Usage: .set <key> <value>. If value is null, unset key.");
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
|
|
|
let key = parts[0];
|
|
|
|
let value = parts[1];
|
|
|
|
match key {
|
2024-04-30 00:57:22 +00:00
|
|
|
"max_output_tokens" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.model.set_max_output_tokens(value);
|
|
|
|
}
|
2023-03-05 14:51:29 +00:00
|
|
|
"temperature" => {
|
2024-03-27 02:02:09 +00:00
|
|
|
let value = parse_value(value)?;
|
2024-03-04 03:08:59 +00:00
|
|
|
self.set_temperature(value);
|
|
|
|
}
|
2024-04-24 08:12:38 +00:00
|
|
|
"top_p" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.set_top_p(value);
|
|
|
|
}
|
2024-03-04 03:08:59 +00:00
|
|
|
"compress_threshold" => {
|
2024-03-27 02:02:09 +00:00
|
|
|
let value = parse_value(value)?;
|
2024-03-04 03:08:59 +00:00
|
|
|
self.set_compress_threshold(value);
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
|
|
|
"save" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.save = value;
|
|
|
|
}
|
2024-03-26 23:33:21 +00:00
|
|
|
"save_session" => {
|
2024-03-27 02:02:09 +00:00
|
|
|
let value = parse_value(value)?;
|
2024-03-26 23:33:21 +00:00
|
|
|
self.set_save_session(value);
|
|
|
|
}
|
2023-03-05 14:51:29 +00:00
|
|
|
"highlight" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.highlight = value;
|
|
|
|
}
|
|
|
|
"dry_run" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.dry_run = value;
|
|
|
|
}
|
2023-11-02 08:08:11 +00:00
|
|
|
"auto_copy" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.auto_copy = value;
|
|
|
|
}
|
2023-10-30 04:58:11 +00:00
|
|
|
_ => bail!("Unknown key `{key}`"),
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
2023-03-09 02:39:28 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-11-02 01:53:54 +00:00
|
|
|
pub fn start_session(&mut self, session: Option<&str>) -> Result<()> {
|
2023-10-28 13:39:17 +00:00
|
|
|
if self.session.is_some() {
|
2023-12-13 00:11:10 +00:00
|
|
|
bail!(
|
|
|
|
"Already in a session, please run '.exit session' first to exit the current session."
|
|
|
|
);
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
match session {
|
|
|
|
None => {
|
|
|
|
let session_file = Self::session_file(TEMP_SESSION_NAME)?;
|
|
|
|
if session_file.exists() {
|
2024-03-26 23:00:28 +00:00
|
|
|
remove_file(session_file).with_context(|| {
|
|
|
|
format!("Failed to cleanup previous '{TEMP_SESSION_NAME}' session")
|
|
|
|
})?;
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2024-03-27 02:25:42 +00:00
|
|
|
let session = Session::new(self, TEMP_SESSION_NAME);
|
2024-03-27 02:02:09 +00:00
|
|
|
self.session = Some(session);
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
Some(name) => {
|
|
|
|
let session_path = Self::session_file(name)?;
|
|
|
|
if !session_path.exists() {
|
2024-03-26 23:33:21 +00:00
|
|
|
self.session = Some(Session::new(self, name));
|
2023-10-28 13:39:17 +00:00
|
|
|
} else {
|
2023-10-30 04:58:11 +00:00
|
|
|
let session = Session::load(name, &session_path)?;
|
2024-03-27 02:25:42 +00:00
|
|
|
let model_id = session.model().to_string();
|
2023-10-28 13:39:17 +00:00
|
|
|
self.session = Some(session);
|
2024-03-27 02:25:42 +00:00
|
|
|
self.set_model(&model_id)?;
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
if session.is_empty() {
|
|
|
|
if let Some((input, output)) = &self.last_message {
|
|
|
|
let ans = Confirm::new(
|
|
|
|
"Start a session that incorporates the last question and answer?",
|
|
|
|
)
|
|
|
|
.with_default(false)
|
2024-02-24 11:13:48 +00:00
|
|
|
.prompt()?;
|
2023-10-28 13:39:17 +00:00
|
|
|
if ans {
|
|
|
|
session.add_message(input, output)?;
|
|
|
|
}
|
|
|
|
}
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-03-27 02:25:42 +00:00
|
|
|
pub fn end_session(&mut self) -> Result<()> {
|
2023-10-28 13:39:17 +00:00
|
|
|
if let Some(mut session) = self.session.take() {
|
|
|
|
self.last_message = None;
|
2024-03-27 02:02:09 +00:00
|
|
|
let save_session = session.save_session();
|
|
|
|
if session.dirty && save_session != Some(false) {
|
2024-03-27 02:25:42 +00:00
|
|
|
if save_session.is_none() || session.is_temp() {
|
2024-04-23 23:16:56 +00:00
|
|
|
if self.working_mode != WorkingMode::Repl {
|
2024-03-26 23:00:28 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let ans = Confirm::new("Save session?").with_default(false).prompt()?;
|
|
|
|
if !ans {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
while session.is_temp() || session.name().is_empty() {
|
|
|
|
session.name = Text::new("Session name:").prompt()?;
|
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2024-03-27 03:20:00 +00:00
|
|
|
Self::save_session_to_file(&mut session)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save_session(&mut self, name: &str) -> Result<()> {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
if !name.is_empty() {
|
|
|
|
session.name = name.to_string();
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2024-03-27 03:20:00 +00:00
|
|
|
Self::save_session_to_file(session)?;
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
|
|
|
|
2024-03-09 14:28:36 +00:00
|
|
|
pub fn has_session(&self) -> bool {
|
|
|
|
self.session.is_some()
|
|
|
|
}
|
|
|
|
|
2024-03-03 07:25:21 +00:00
|
|
|
pub fn clear_session_messages(&mut self) -> Result<()> {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-03-04 03:08:59 +00:00
|
|
|
session.clear_messages();
|
2024-03-03 07:25:21 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-11-02 07:33:22 +00:00
|
|
|
pub fn list_sessions(&self) -> Vec<String> {
|
|
|
|
let sessions_dir = match Self::sessions_dir() {
|
|
|
|
Ok(dir) => dir,
|
|
|
|
Err(_) => return vec![],
|
|
|
|
};
|
2023-11-27 06:04:50 +00:00
|
|
|
match read_dir(sessions_dir) {
|
2023-10-28 13:39:17 +00:00
|
|
|
Ok(rd) => {
|
|
|
|
let mut names = vec![];
|
2023-11-02 07:33:22 +00:00
|
|
|
for entry in rd.flatten() {
|
2023-10-28 13:39:17 +00:00
|
|
|
let name = entry.file_name();
|
|
|
|
if let Some(name) = name.to_string_lossy().strip_suffix(".yaml") {
|
|
|
|
names.push(name.to_string());
|
|
|
|
}
|
|
|
|
}
|
2023-11-27 09:22:16 +00:00
|
|
|
names.sort_unstable();
|
2023-11-02 07:33:22 +00:00
|
|
|
names
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2023-11-02 07:33:22 +00:00
|
|
|
Err(_) => vec![],
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
|
|
|
|
2024-03-04 03:08:59 +00:00
|
|
|
pub fn should_compress_session(&mut self) -> bool {
|
2024-03-06 01:46:47 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
if session.need_compress(self.compress_threshold) {
|
|
|
|
session.compressing = true;
|
2024-03-04 03:08:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn compress_session(&mut self, summary: &str) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-04-23 06:32:06 +00:00
|
|
|
let summary_prompt = self.summary_prompt.as_deref().unwrap_or(SUMMARY_PROMPT);
|
|
|
|
session.compress(format!("{}{}", summary_prompt, summary));
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 06:32:06 +00:00
|
|
|
pub fn summarize_prompt(&self) -> &str {
|
|
|
|
self.summarize_prompt.as_deref().unwrap_or(SUMMARIZE_PROMPT)
|
|
|
|
}
|
|
|
|
|
2024-03-04 03:08:59 +00:00
|
|
|
pub fn is_compressing_session(&self) -> bool {
|
|
|
|
self.session
|
|
|
|
.as_ref()
|
|
|
|
.map(|v| v.compressing)
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn end_compressing_session(&mut self) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.compressing = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-31 01:06:41 +00:00
|
|
|
pub fn get_render_options(&self) -> Result<RenderOptions> {
|
|
|
|
let theme = if self.highlight {
|
|
|
|
let theme_mode = if self.light_theme { "light" } else { "dark" };
|
|
|
|
let theme_filename = format!("{theme_mode}.tmTheme");
|
|
|
|
let theme_path = Self::local_path(&theme_filename)?;
|
|
|
|
if theme_path.exists() {
|
|
|
|
let theme = ThemeSet::get_theme(&theme_path)
|
|
|
|
.with_context(|| format!("Invalid theme at {}", theme_path.display()))?;
|
|
|
|
Some(theme)
|
|
|
|
} else {
|
|
|
|
let theme = if self.light_theme {
|
|
|
|
bincode::deserialize_from(LIGHT_THEME).expect("Invalid builtin light theme")
|
|
|
|
} else {
|
|
|
|
bincode::deserialize_from(DARK_THEME).expect("Invalid builtin dark theme")
|
|
|
|
};
|
|
|
|
Some(theme)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2023-10-30 09:28:10 +00:00
|
|
|
let wrap = if stdout().is_terminal() {
|
|
|
|
self.wrap.clone()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2024-03-19 03:27:20 +00:00
|
|
|
let truecolor = matches!(
|
|
|
|
env::var("COLORTERM").as_ref().map(|v| v.as_str()),
|
|
|
|
Ok("truecolor")
|
|
|
|
);
|
|
|
|
Ok(RenderOptions::new(theme, wrap, self.wrap_code, truecolor))
|
2023-03-11 13:45:34 +00:00
|
|
|
}
|
|
|
|
|
2023-12-24 08:04:18 +00:00
|
|
|
pub fn render_prompt_left(&self) -> String {
|
|
|
|
let variables = self.generate_prompt_context();
|
2024-04-23 06:32:06 +00:00
|
|
|
let left_prompt = self.left_prompt.as_deref().unwrap_or(LEFT_PROMPT);
|
|
|
|
render_prompt(left_prompt, &variables)
|
2023-12-24 08:04:18 +00:00
|
|
|
}
|
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
pub fn render_prompt_right(&self) -> String {
|
2023-12-24 08:04:18 +00:00
|
|
|
let variables = self.generate_prompt_context();
|
2024-04-23 06:32:06 +00:00
|
|
|
let right_prompt = self.right_prompt.as_deref().unwrap_or(RIGHT_PROMPT);
|
|
|
|
render_prompt(right_prompt, &variables)
|
2023-11-01 02:28:54 +00:00
|
|
|
}
|
|
|
|
|
2023-11-27 06:04:50 +00:00
|
|
|
pub fn prepare_send_data(&self, input: &Input, stream: bool) -> Result<SendData> {
|
|
|
|
let messages = self.build_messages(input)?;
|
2024-03-27 00:11:00 +00:00
|
|
|
let temperature = if let Some(session) = input.session(&self.session) {
|
|
|
|
session.temperature()
|
|
|
|
} else if let Some(role) = input.role() {
|
|
|
|
role.temperature
|
|
|
|
} else {
|
|
|
|
self.temperature
|
|
|
|
};
|
2024-04-24 08:12:38 +00:00
|
|
|
let top_p = if let Some(session) = input.session(&self.session) {
|
|
|
|
session.top_p()
|
|
|
|
} else if let Some(role) = input.role() {
|
|
|
|
role.top_p
|
|
|
|
} else {
|
|
|
|
self.top_p
|
|
|
|
};
|
2024-03-06 00:35:40 +00:00
|
|
|
self.model.max_input_tokens_limit(&messages)?;
|
2023-10-31 08:50:08 +00:00
|
|
|
Ok(SendData {
|
|
|
|
messages,
|
2024-03-27 00:11:00 +00:00
|
|
|
temperature,
|
2024-04-24 08:12:38 +00:00
|
|
|
top_p,
|
2023-10-31 08:50:08 +00:00
|
|
|
stream,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-03-10 00:01:54 +00:00
|
|
|
pub fn input_context(&self) -> InputContext {
|
|
|
|
InputContext::new(self.role.clone(), self.has_session())
|
|
|
|
}
|
|
|
|
|
2023-11-27 06:04:50 +00:00
|
|
|
pub fn maybe_print_send_tokens(&self, input: &Input) {
|
2023-03-20 14:51:51 +00:00
|
|
|
if self.dry_run {
|
|
|
|
if let Ok(messages) = self.build_messages(input) {
|
2023-11-02 22:52:57 +00:00
|
|
|
let tokens = self.model.total_tokens(&messages);
|
2023-10-27 01:33:34 +00:00
|
|
|
println!(">>> This message consumes {tokens} tokens. <<<");
|
2023-03-20 14:51:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-24 08:04:18 +00:00
|
|
|
fn generate_prompt_context(&self) -> HashMap<&str, String> {
|
|
|
|
let mut output = HashMap::new();
|
|
|
|
output.insert("model", self.model.id());
|
|
|
|
output.insert("client_name", self.model.client_name.clone());
|
|
|
|
output.insert("model_name", self.model.name.clone());
|
|
|
|
output.insert(
|
2024-03-06 00:35:40 +00:00
|
|
|
"max_input_tokens",
|
|
|
|
self.model.max_input_tokens.unwrap_or_default().to_string(),
|
2023-12-24 08:04:18 +00:00
|
|
|
);
|
|
|
|
if let Some(temperature) = self.temperature {
|
|
|
|
if temperature != 0.0 {
|
|
|
|
output.insert("temperature", temperature.to_string());
|
|
|
|
}
|
|
|
|
}
|
2024-04-24 08:12:38 +00:00
|
|
|
if let Some(top_p) = self.top_p {
|
|
|
|
if top_p != 0.0 {
|
|
|
|
output.insert("top_p", top_p.to_string());
|
|
|
|
}
|
|
|
|
}
|
2023-12-24 08:04:18 +00:00
|
|
|
if self.dry_run {
|
|
|
|
output.insert("dry_run", "true".to_string());
|
|
|
|
}
|
|
|
|
if self.save {
|
|
|
|
output.insert("save", "true".to_string());
|
|
|
|
}
|
|
|
|
if let Some(wrap) = &self.wrap {
|
|
|
|
if wrap != "no" {
|
|
|
|
output.insert("wrap", wrap.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if self.auto_copy {
|
|
|
|
output.insert("auto_copy", "true".to_string());
|
|
|
|
}
|
|
|
|
if let Some(role) = &self.role {
|
|
|
|
output.insert("role", role.name.clone());
|
|
|
|
}
|
|
|
|
if let Some(session) = &self.session {
|
|
|
|
output.insert("session", session.name().to_string());
|
2024-03-27 02:02:09 +00:00
|
|
|
output.insert("dirty", session.dirty.to_string());
|
2023-12-24 08:04:18 +00:00
|
|
|
let (tokens, percent) = session.tokens_and_percent();
|
|
|
|
output.insert("consume_tokens", tokens.to_string());
|
|
|
|
output.insert("consume_percent", percent.to_string());
|
|
|
|
output.insert("user_messages_len", session.user_messages_len().to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.highlight {
|
|
|
|
output.insert("color.reset", "\u{1b}[0m".to_string());
|
|
|
|
output.insert("color.black", "\u{1b}[30m".to_string());
|
|
|
|
output.insert("color.dark_gray", "\u{1b}[90m".to_string());
|
|
|
|
output.insert("color.red", "\u{1b}[31m".to_string());
|
|
|
|
output.insert("color.light_red", "\u{1b}[91m".to_string());
|
|
|
|
output.insert("color.green", "\u{1b}[32m".to_string());
|
|
|
|
output.insert("color.light_green", "\u{1b}[92m".to_string());
|
|
|
|
output.insert("color.yellow", "\u{1b}[33m".to_string());
|
|
|
|
output.insert("color.light_yellow", "\u{1b}[93m".to_string());
|
|
|
|
output.insert("color.blue", "\u{1b}[34m".to_string());
|
|
|
|
output.insert("color.light_blue", "\u{1b}[94m".to_string());
|
|
|
|
output.insert("color.purple", "\u{1b}[35m".to_string());
|
|
|
|
output.insert("color.light_purple", "\u{1b}[95m".to_string());
|
|
|
|
output.insert("color.magenta", "\u{1b}[35m".to_string());
|
|
|
|
output.insert("color.light_magenta", "\u{1b}[95m".to_string());
|
|
|
|
output.insert("color.cyan", "\u{1b}[36m".to_string());
|
|
|
|
output.insert("color.light_cyan", "\u{1b}[96m".to_string());
|
|
|
|
output.insert("color.white", "\u{1b}[37m".to_string());
|
|
|
|
output.insert("color.light_gray", "\u{1b}[97m".to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
output
|
|
|
|
}
|
|
|
|
|
2023-03-08 03:27:51 +00:00
|
|
|
fn open_message_file(&self) -> Result<File> {
|
2023-07-10 03:07:09 +00:00
|
|
|
let path = Self::messages_file()?;
|
2023-03-11 03:26:24 +00:00
|
|
|
ensure_parent_exists(&path)?;
|
2023-03-08 03:27:51 +00:00
|
|
|
OpenOptions::new()
|
|
|
|
.create(true)
|
|
|
|
.append(true)
|
|
|
|
.open(&path)
|
|
|
|
.with_context(|| format!("Failed to create/append {}", path.display()))
|
|
|
|
}
|
|
|
|
|
2024-03-27 03:20:00 +00:00
|
|
|
fn save_session_to_file(session: &mut Session) -> Result<()> {
|
|
|
|
let session_path = Self::session_file(session.name())?;
|
|
|
|
let sessions_dir = session_path
|
|
|
|
.parent()
|
|
|
|
.ok_or_else(|| anyhow!("Unable to save session file to {}", session_path.display()))?;
|
|
|
|
if !sessions_dir.exists() {
|
|
|
|
create_dir_all(sessions_dir).with_context(|| {
|
|
|
|
format!("Failed to create session_dir '{}'", sessions_dir.display())
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
session.save(&session_path)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-04-28 05:28:24 +00:00
|
|
|
fn load_config_file(config_path: &Path) -> Result<Self> {
|
2023-11-01 02:28:54 +00:00
|
|
|
let ctx = || format!("Failed to load config at {}", config_path.display());
|
|
|
|
let content = read_to_string(config_path).with_context(ctx)?;
|
2024-04-28 05:28:24 +00:00
|
|
|
let config = Self::load_config(&content).with_context(ctx)?;
|
|
|
|
Ok(config)
|
|
|
|
}
|
2023-03-11 03:26:24 +00:00
|
|
|
|
2024-04-28 05:28:24 +00:00
|
|
|
fn load_config_env(client_type: &str) -> Result<Self> {
|
|
|
|
let model_id = match env::var(get_env_name("model_name")) {
|
|
|
|
Ok(model_name) => format!("{client_type}:{model_name}"),
|
|
|
|
Err(_) => client_type.to_string(),
|
|
|
|
};
|
|
|
|
let content = format!(
|
|
|
|
r#"
|
|
|
|
model: {model_id}
|
|
|
|
save: false
|
|
|
|
clients:
|
|
|
|
- type: {client_type}
|
|
|
|
"#
|
|
|
|
);
|
|
|
|
let config = Self::load_config(&content).with_context(|| "Failed to load config")?;
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_config(content: &str) -> Result<Self> {
|
|
|
|
let config: Self = serde_yaml::from_str(content).map_err(|err| {
|
|
|
|
let err_msg = err.to_string();
|
|
|
|
if err_msg.starts_with(&format!("{}: ", CLIENTS_FIELD)) {
|
|
|
|
anyhow!("clients: invalid value")
|
|
|
|
} else {
|
|
|
|
anyhow!("{err_msg}")
|
|
|
|
}
|
|
|
|
})?;
|
2023-11-01 02:28:54 +00:00
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
|
2023-03-03 01:38:53 +00:00
|
|
|
fn load_roles(&mut self) -> Result<()> {
|
2023-03-03 04:43:34 +00:00
|
|
|
let path = Self::roles_file()?;
|
2023-03-03 01:38:53 +00:00
|
|
|
if !path.exists() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let content = read_to_string(&path)
|
2023-03-05 08:45:42 +00:00
|
|
|
.with_context(|| format!("Failed to load roles at {}", path.display()))?;
|
2023-03-03 01:38:53 +00:00
|
|
|
let roles: Vec<Role> =
|
2023-03-05 08:45:42 +00:00
|
|
|
serde_yaml::from_str(&content).with_context(|| "Invalid roles config")?;
|
2023-03-03 01:38:53 +00:00
|
|
|
self.roles = roles;
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-03-11 03:26:24 +00:00
|
|
|
|
2023-11-02 22:52:57 +00:00
|
|
|
fn setup_model(&mut self) -> Result<()> {
|
|
|
|
let model = match &self.model_id {
|
2023-10-31 08:50:08 +00:00
|
|
|
Some(v) => v.clone(),
|
|
|
|
None => {
|
2023-11-02 07:33:22 +00:00
|
|
|
let models = list_models(self);
|
2023-10-31 08:50:08 +00:00
|
|
|
if models.is_empty() {
|
|
|
|
bail!("No available model");
|
|
|
|
}
|
|
|
|
|
2023-11-02 07:33:22 +00:00
|
|
|
models[0].id()
|
2023-10-31 08:50:08 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
self.set_model(&model)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
fn setup_highlight(&mut self) {
|
2023-03-11 13:45:34 +00:00
|
|
|
if let Ok(value) = env::var("NO_COLOR") {
|
|
|
|
let mut no_color = false;
|
|
|
|
set_bool(&mut no_color, &value);
|
|
|
|
if no_color {
|
|
|
|
self.highlight = false;
|
|
|
|
}
|
|
|
|
}
|
2023-03-11 03:26:24 +00:00
|
|
|
}
|
2023-03-12 00:31:06 +00:00
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
fn setup_light_theme(&mut self) -> Result<()> {
|
2023-10-29 09:22:23 +00:00
|
|
|
if self.light_theme {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
if let Ok(value) = env::var(get_env_name("light_theme")) {
|
|
|
|
set_bool(&mut self.light_theme, &value);
|
|
|
|
return Ok(());
|
2023-10-30 03:45:07 +00:00
|
|
|
} else if let Ok(value) = env::var("COLORFGBG") {
|
|
|
|
if let Some(light) = light_theme_from_colorfgbg(&value) {
|
|
|
|
self.light_theme = light
|
|
|
|
}
|
|
|
|
};
|
2023-10-29 09:22:23 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2023-03-02 11:52:11 +00:00
|
|
|
}
|
|
|
|
|
2023-10-27 13:22:24 +00:00
|
|
|
#[derive(Debug, Clone, Deserialize, Default)]
|
|
|
|
pub enum Keybindings {
|
|
|
|
#[serde(rename = "emacs")]
|
|
|
|
#[default]
|
|
|
|
Emacs,
|
|
|
|
#[serde(rename = "vi")]
|
|
|
|
Vi,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Keybindings {
|
|
|
|
pub fn is_vi(&self) -> bool {
|
|
|
|
matches!(self, Keybindings::Vi)
|
|
|
|
}
|
|
|
|
pub fn stringify(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
Keybindings::Emacs => "emacs",
|
|
|
|
Keybindings::Vi => "vi",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 23:16:56 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub enum WorkingMode {
|
|
|
|
Command,
|
|
|
|
Repl,
|
|
|
|
Serve,
|
|
|
|
}
|
|
|
|
|
2024-03-03 06:52:15 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
2023-11-27 07:39:55 +00:00
|
|
|
pub enum State {
|
|
|
|
Normal,
|
|
|
|
Role,
|
|
|
|
EmptySession,
|
|
|
|
EmptySessionWithRole,
|
|
|
|
Session,
|
|
|
|
}
|
|
|
|
|
2024-03-03 06:52:15 +00:00
|
|
|
impl State {
|
|
|
|
pub fn all() -> Vec<Self> {
|
|
|
|
vec![
|
|
|
|
Self::Normal,
|
|
|
|
Self::Role,
|
|
|
|
Self::EmptySession,
|
|
|
|
Self::EmptySessionWithRole,
|
|
|
|
Self::Session,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn in_session() -> Vec<Self> {
|
|
|
|
vec![
|
|
|
|
Self::EmptySession,
|
|
|
|
Self::EmptySessionWithRole,
|
|
|
|
Self::Session,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2024-03-07 12:31:06 +00:00
|
|
|
pub fn not_in_session() -> Vec<Self> {
|
2024-03-03 06:52:15 +00:00
|
|
|
let excludes: HashSet<_> = Self::in_session().into_iter().collect();
|
|
|
|
Self::all()
|
|
|
|
.into_iter()
|
|
|
|
.filter(|v| !excludes.contains(v))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2024-03-03 07:25:21 +00:00
|
|
|
pub fn unable_change_role() -> Vec<Self> {
|
|
|
|
vec![Self::Session]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn able_change_role() -> Vec<Self> {
|
|
|
|
let excludes: HashSet<_> = Self::unable_change_role().into_iter().collect();
|
2024-03-03 06:52:15 +00:00
|
|
|
Self::all()
|
|
|
|
.into_iter()
|
2024-03-03 07:25:21 +00:00
|
|
|
.filter(|v| !excludes.contains(v))
|
2024-03-03 06:52:15 +00:00
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn in_role() -> Vec<Self> {
|
|
|
|
vec![Self::Role, Self::EmptySessionWithRole]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 09:53:29 +00:00
|
|
|
fn create_config_file(config_path: &Path) -> Result<()> {
|
|
|
|
let ans = Confirm::new("No config file, create a new one?")
|
|
|
|
.with_default(true)
|
2024-02-24 11:13:48 +00:00
|
|
|
.prompt()?;
|
2023-03-03 09:53:29 +00:00
|
|
|
if !ans {
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2024-02-24 11:13:48 +00:00
|
|
|
let client = Select::new("Platform:", list_client_types()).prompt()?;
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
let mut config = serde_json::json!({});
|
2024-03-25 03:13:54 +00:00
|
|
|
let (model, clients_config) = create_client_config(client)?;
|
|
|
|
config["model"] = model.into();
|
|
|
|
config[CLIENTS_FIELD] = clients_config;
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
let config_data = serde_yaml::to_string(&config).with_context(|| "Failed to create config")?;
|
2023-03-03 09:53:29 +00:00
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
ensure_parent_exists(config_path)?;
|
2023-11-01 02:28:54 +00:00
|
|
|
std::fs::write(config_path, config_data).with_context(|| "Failed to write to config file")?;
|
2023-05-04 08:29:34 +00:00
|
|
|
#[cfg(unix)]
|
|
|
|
{
|
|
|
|
use std::os::unix::prelude::PermissionsExt;
|
|
|
|
let perms = std::fs::Permissions::from_mode(0o600);
|
|
|
|
std::fs::set_permissions(config_path, perms)?;
|
|
|
|
}
|
2023-10-31 09:34:16 +00:00
|
|
|
|
|
|
|
println!("✨ Saved config file to {}\n", config_path.display());
|
|
|
|
|
2023-03-03 09:53:29 +00:00
|
|
|
Ok(())
|
2023-03-02 11:52:11 +00:00
|
|
|
}
|
2023-03-05 11:48:22 +00:00
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
fn ensure_parent_exists(path: &Path) -> Result<()> {
|
|
|
|
if path.exists() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let parent = path
|
|
|
|
.parent()
|
|
|
|
.ok_or_else(|| anyhow!("Failed to write to {}, No parent path", path.display()))?;
|
|
|
|
if !parent.exists() {
|
|
|
|
create_dir_all(parent).with_context(|| {
|
|
|
|
format!(
|
|
|
|
"Failed to write {}, Cannot create parent directory",
|
|
|
|
path.display()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-03-11 10:53:36 +00:00
|
|
|
fn set_bool(target: &mut bool, value: &str) {
|
|
|
|
match value {
|
|
|
|
"1" | "true" => *target = true,
|
|
|
|
"0" | "false" => *target = false,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
2023-11-02 13:38:01 +00:00
|
|
|
|
2024-03-27 02:02:09 +00:00
|
|
|
fn parse_value<T>(value: &str) -> Result<Option<T>>
|
|
|
|
where
|
|
|
|
T: std::str::FromStr,
|
|
|
|
{
|
|
|
|
let value = if value == "null" {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let value = match value.parse() {
|
|
|
|
Ok(value) => value,
|
|
|
|
Err(_) => bail!("Invalid value '{}'", value),
|
|
|
|
};
|
|
|
|
Some(value)
|
|
|
|
};
|
|
|
|
Ok(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn complete_bool(value: bool) -> Vec<String> {
|
|
|
|
vec![(!value).to_string()]
|
|
|
|
}
|
|
|
|
|
|
|
|
fn complete_option_bool(value: Option<bool>) -> Vec<String> {
|
|
|
|
match value {
|
|
|
|
Some(true) => vec!["false".to_string(), "null".to_string()],
|
|
|
|
Some(false) => vec!["true".to_string(), "null".to_string()],
|
|
|
|
None => vec!["true".to_string(), "false".to_string()],
|
|
|
|
}
|
|
|
|
}
|