Skip to content

Commit

Permalink
Make history persistent (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xHumban authored Dec 5, 2024
1 parent 6814e35 commit 0f3181a
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 69 deletions.
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<br>

Expand Down Expand Up @@ -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"
```

Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down
15 changes: 0 additions & 15 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand All @@ -24,10 +21,6 @@ pub struct Config {
pub ollama: Option<OllamaConfig>,
}

pub fn default_archive_file_name() -> String {
String::from("tenere.archive")
}

pub fn default_llm_backend() -> LLMBackend {
LLMBackend::ChatGPT
}
Expand Down Expand Up @@ -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,
}
Expand All @@ -104,7 +94,6 @@ impl Default for KeyBindings {
show_help: '?',
show_history: 'h',
new_chat: 'n',
save_chat: 's',
stop_stream: 't',
}
}
Expand All @@ -123,10 +112,6 @@ impl KeyBindings {
'n'
}

fn default_save_chat() -> char {
's'
}

fn default_stop_stream() -> char {
't'
}
Expand Down
37 changes: 2 additions & 35 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();

Expand All @@ -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 => {
Expand Down
4 changes: 0 additions & 4 deletions src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
64 changes: 55 additions & 9 deletions src/history.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use core::str;
use std::{fs, path::PathBuf};

use tokio::sync::mpsc::UnboundedSender;

use ratatui::{
Expand Down Expand Up @@ -81,17 +84,60 @@ impl History<'_> {
self.state.select(Some(i));
}

pub fn save(&mut self, archive_file_name: &str, sender: UnboundedSender<Event>) {
// check if data directory for the application exists, else it will create it
pub fn check_data_directory_exists(&self, sender: UnboundedSender<Event>) {
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<Event>) {
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<Event>) {
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();
}
Expand Down
7 changes: 7 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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? {
Expand Down

0 comments on commit 0f3181a

Please sign in to comment.