diff --git a/README.md b/README.md
index 423869d..84faf13 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@
- Vim keybinding (most common ops)
- Copy text from/to clipboard (works only on the prompt)
- Multiple backends
+- Automatically load the last saved chat into history
@@ -84,14 +85,12 @@ tenere -c ~/path/to/custom/config.toml
Here are the available general settings:
-- `archive_file_name`: the file name where the chat will be saved. By default it is set to `tenere.archive`
- `llm`: the llm model name. Possible values are:
- `chatgpt`
- `llamacpp`
- `ollama`
```toml
-archive_file_name = "tenere.archive"
llm = "chatgpt"
```
@@ -106,7 +105,6 @@ Here is an example with the default key bindings
show_help = '?'
show_history = 'h'
new_chat = 'n'
-save_chat = 's'
```
ℹ️ Note
@@ -185,9 +183,7 @@ More infos about ollama api [here](https://github.com/ollama/ollama/blob/main/do
These are the default key bindings regardless of the focused block.
-`ctrl + n`: Start a new chat and save the previous one in history.
-
-`ctrl + s`: Save the current chat or chat history (history pop-up should be visible first) to `tenere.archive` file in the current directory.
+`ctrl + n`: Start a new chat and save the previous one in history and save it to `tenere.archive-i` file in `data directory`.
`Tab`: Switch the focus.
diff --git a/src/config.rs b/src/config.rs
index f3a608a..00cb77e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -7,9 +7,6 @@ use std::path::PathBuf;
#[derive(Deserialize, Debug)]
pub struct Config {
- #[serde(default = "default_archive_file_name")]
- pub archive_file_name: String,
-
#[serde(default)]
pub key_bindings: KeyBindings,
@@ -24,10 +21,6 @@ pub struct Config {
pub ollama: Option,
}
-pub fn default_archive_file_name() -> String {
- String::from("tenere.archive")
-}
-
pub fn default_llm_backend() -> LLMBackend {
LLMBackend::ChatGPT
}
@@ -91,9 +84,6 @@ pub struct KeyBindings {
#[serde(default = "KeyBindings::default_new_chat")]
pub new_chat: char,
- #[serde(default = "KeyBindings::default_save_chat")]
- pub save_chat: char,
-
#[serde(default = "KeyBindings::default_stop_stream")]
pub stop_stream: char,
}
@@ -104,7 +94,6 @@ impl Default for KeyBindings {
show_help: '?',
show_history: 'h',
new_chat: 'n',
- save_chat: 's',
stop_stream: 't',
}
}
@@ -123,10 +112,6 @@ impl KeyBindings {
'n'
}
- fn default_save_chat() -> char {
- 's'
- }
-
fn default_stop_stream() -> char {
't'
}
diff --git a/src/handler.rs b/src/handler.rs
index 0a2453f..91c864c 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -11,7 +11,6 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::text::Line;
-use crate::notification::{Notification, NotificationLevel};
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -111,6 +110,8 @@ pub async fn handle_key_events(
.push(app.chat.formatted_chat.clone());
app.history.text.push(app.chat.plain_chat.clone());
+ // after adding to history, save the chat in file
+ app.history.save(app.history.text.len() - 1, sender.clone());
app.chat = Chat::default();
@@ -123,40 +124,6 @@ pub async fn handle_key_events(
app.chat.scroll = 0;
}
- // Save chat
- KeyCode::Char(c)
- if c == app.config.key_bindings.save_chat
- && key_event.modifiers == KeyModifiers::CONTROL =>
- {
- match app.focused_block {
- FocusedBlock::History | FocusedBlock::Preview => {
- app.history
- .save(app.config.archive_file_name.as_str(), sender.clone());
- }
- FocusedBlock::Chat | FocusedBlock::Prompt => {
- match std::fs::write(
- app.config.archive_file_name.clone(),
- app.chat.plain_chat.join(""),
- ) {
- Ok(_) => {
- let notif = Notification::new(
- format!("Chat saved to `{}` file", app.config.archive_file_name),
- NotificationLevel::Info,
- );
-
- sender.send(Event::Notification(notif)).unwrap();
- }
- Err(e) => {
- let notif = Notification::new(e.to_string(), NotificationLevel::Error);
-
- sender.send(Event::Notification(notif)).unwrap();
- }
- }
- }
- _ => (),
- }
- }
-
// Switch the focus
KeyCode::Tab => match app.focused_block {
FocusedBlock::Chat => {
diff --git a/src/help.rs b/src/help.rs
index f53b195..537d8b5 100644
--- a/src/help.rs
+++ b/src/help.rs
@@ -24,10 +24,6 @@ impl Default for Help {
Cell::from("ctrl + n").bold().yellow(),
"Start new chat and save the previous one to the history",
),
- (
- Cell::from("ctrl + s").bold().yellow(),
- "Save the chat to file in the current directory",
- ),
(Cell::from("ctrl + h").bold().yellow(), "Show history"),
(
Cell::from("ctrl + t").bold().yellow(),
diff --git a/src/history.rs b/src/history.rs
index 1e9180e..9510e5b 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -1,3 +1,6 @@
+use core::str;
+use std::{fs, path::PathBuf};
+
use tokio::sync::mpsc::UnboundedSender;
use ratatui::{
@@ -81,17 +84,60 @@ impl History<'_> {
self.state.select(Some(i));
}
- pub fn save(&mut self, archive_file_name: &str, sender: UnboundedSender) {
+ // check if data directory for the application exists, else it will create it
+ pub fn check_data_directory_exists(&self, sender: UnboundedSender) {
+ if let Some(data_directory) = dirs::data_dir() {
+ let target_directory = data_directory.join("tenere");
+
+ if !target_directory.exists() {
+ if let Err(e) = fs::create_dir_all(target_directory) {
+ let notif = Notification::new(e.to_string(), NotificationLevel::Error);
+ sender.send(Event::Notification(notif)).unwrap();
+ }
+ }
+ }
+ }
+
+ // load chat in the history from data directory
+ pub fn load_history(&mut self, sender: UnboundedSender) {
+ let directory_path: PathBuf = dirs::data_dir().unwrap().join("tenere");
+
+ if let Ok(paths) = fs::read_dir(directory_path.clone()) {
+ // foreach archive file we add it to history
+ for path in paths {
+ if path.as_ref().unwrap().file_type().unwrap().is_file() {
+ self.load_chat_from_file(path.unwrap().path().to_str().unwrap());
+ }
+ }
+
+ let notif = Notification::new("History loaded".to_string(), NotificationLevel::Info);
+
+ sender.send(Event::Notification(notif)).unwrap();
+ }
+ }
+
+ /// Add to history the archive file if exists
+ pub fn load_chat_from_file(&mut self, archive_file_name: &str) {
+ if let Ok(text) = std::fs::read_to_string(archive_file_name) {
+ // push full conversation in preview
+ self.preview.text.push(Text::from(text.clone()));
+ // get first line of the conversation
+ let first_line: String = text.lines().next().unwrap_or("").to_string();
+ self.text.push(vec![first_line]);
+ }
+ }
+
+ // call after adding new chat in history (Starting a new chat)
+ // with the index of the chat in history to save
+ pub fn save(&mut self, chat_index_in_history: usize, sender: UnboundedSender) {
+ let file_name = format!("tenere.archive-{}", chat_index_in_history);
+ let file_path: PathBuf = dirs::data_dir().unwrap().join("tenere").join(file_name);
+
if !self.text.is_empty() {
- match std::fs::write(
- archive_file_name,
- self.text[self.state.selected().unwrap_or(0)].join(""),
- ) {
+ match std::fs::write(file_path.clone(), self.text[chat_index_in_history].join("")) {
Ok(_) => {
- let notif = Notification::new(
- format!("Chat saved to `{}` file", archive_file_name),
- NotificationLevel::Info,
- );
+ let notif =
+ Notification::new("Chat saved".to_string(), NotificationLevel::Info);
sender.send(Event::Notification(notif)).unwrap();
}
diff --git a/src/main.rs b/src/main.rs
index 680f452..96fe544 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -48,6 +48,13 @@ async fn main() -> AppResult<()> {
let mut tui = Tui::new(terminal, events);
tui.init()?;
+ // create data directory if not exists
+ app.history
+ .check_data_directory_exists(tui.events.sender.clone());
+
+ // load potential history data from archive files
+ app.history.load_history(tui.events.sender.clone());
+
while app.running {
tui.draw(&mut app)?;
match tui.events.next().await? {