Skip to content

Commit 4345f5c

Browse files
authored
Add ability to sort prompts in the left panel by history count and last usage time (#34)
* Add ability to sort prompts in the left panel by history count * Add sorting by last usage (WIP) * Save next bit of a progress (WIP) * Add unsorted mode * Move prompts sorting logic to a separate method * Remove unnecesary mark_last_used method * Add unit tests for sort_prompt_indices (WIP) * Cleanup code after AI * Replace toggle button with selector for sort prompts by * Make history size indicator better looking (WIP) * Make linter happy
1 parent 4457091 commit 4345f5c

File tree

2 files changed

+147
-27
lines changed

2 files changed

+147
-27
lines changed

src/app.rs

Lines changed: 121 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ use crate::{
1616
pub const TITLE: &str = "Reprompt";
1717
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
1818

19+
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq)]
20+
pub enum SortMode {
21+
HistoryCount,
22+
LastUsage,
23+
InsertionOrder,
24+
}
25+
26+
impl Default for SortMode {
27+
fn default() -> Self {
28+
Self::InsertionOrder
29+
}
30+
}
31+
1932
#[derive(serde::Serialize, serde::Deserialize)]
2033
#[serde(default)]
2134
pub struct App {
@@ -29,6 +42,8 @@ pub struct App {
2942
ollama_client: OllamaClient,
3043
#[serde(skip)]
3144
commonmark_cache: CommonMarkCache,
45+
#[serde(skip)]
46+
sort_mode: SortMode,
3247
}
3348

3449
impl Default for App {
@@ -44,6 +59,7 @@ impl Default for App {
4459
ollama_client: OllamaClient::new(Ollama::default()),
4560
ollama_models: Default::default(),
4661
commonmark_cache: CommonMarkCache::default(),
62+
sort_mode: SortMode::InsertionOrder,
4763
}
4864
}
4965
}
@@ -225,27 +241,27 @@ impl App {
225241
}
226242
}
227243
AppAction::GeneratePromptResponse { idx, input } => {
228-
if let Some(selected_model) = &self.ollama_models.selected {
229-
if let Some(prompt) = self.prompts.get_mut(idx) {
230-
prompt.generate_response(
231-
input,
232-
selected_model,
233-
&self.tokio_runtime,
234-
&self.ollama_client,
235-
);
236-
}
244+
if let Some(selected_model) = &self.ollama_models.selected
245+
&& let Some(prompt) = self.prompts.get_mut(idx)
246+
{
247+
prompt.generate_response(
248+
input,
249+
selected_model,
250+
&self.tokio_runtime,
251+
&self.ollama_client,
252+
);
237253
}
238254
}
239255
AppAction::RegeneratePromptResponse { idx, history_idx } => {
240-
if let Some(selected_model) = &self.ollama_models.selected {
241-
if let Some(prompt) = self.prompts.get_mut(idx) {
242-
prompt.regenerate_response(
243-
history_idx,
244-
selected_model,
245-
&self.tokio_runtime,
246-
&self.ollama_client,
247-
);
248-
}
256+
if let Some(selected_model) = &self.ollama_models.selected
257+
&& let Some(prompt) = self.prompts.get_mut(idx)
258+
{
259+
prompt.regenerate_response(
260+
history_idx,
261+
selected_model,
262+
&self.tokio_runtime,
263+
&self.ollama_client,
264+
);
249265
}
250266
}
251267
AppAction::CloseDialog => {
@@ -442,7 +458,9 @@ impl App {
442458
ui.horizontal_top(|ui| {
443459
assign_if_some!(action, self.show_left_panel_create_protmp_button(ui));
444460

445-
assign_if_some!(action, self.show_left_panel_model_selector(ui))
461+
assign_if_some!(action, self.show_left_panel_model_selector(ui));
462+
463+
self.show_left_panel_sort_mode_selector(ui);
446464
});
447465

448466
ui.separator();
@@ -557,6 +575,85 @@ impl App {
557575
action
558576
}
559577

578+
fn show_left_panel_sort_mode_selector(&mut self, ui: &mut egui::Ui) {
579+
ui.horizontal(|ui| {
580+
ui.label("Sort by:");
581+
582+
egui::ComboBox::from_id_salt("sort_mode_selector")
583+
.selected_text(match self.sort_mode {
584+
SortMode::InsertionOrder => "insertion",
585+
SortMode::HistoryCount => "history count",
586+
SortMode::LastUsage => "last usage",
587+
})
588+
.show_ui(ui, |ui| {
589+
if ui
590+
.selectable_label(
591+
matches!(self.sort_mode, SortMode::InsertionOrder),
592+
"insertion (default)",
593+
)
594+
.clicked()
595+
{
596+
self.sort_mode = SortMode::InsertionOrder;
597+
}
598+
if ui
599+
.selectable_label(
600+
matches!(self.sort_mode, SortMode::HistoryCount),
601+
"history count",
602+
)
603+
.clicked()
604+
{
605+
self.sort_mode = SortMode::HistoryCount;
606+
}
607+
if ui
608+
.selectable_label(
609+
matches!(self.sort_mode, SortMode::LastUsage),
610+
"last usage",
611+
)
612+
.clicked()
613+
{
614+
self.sort_mode = SortMode::LastUsage;
615+
}
616+
});
617+
});
618+
}
619+
620+
/// Sorts prompt indices based on the current sort mode
621+
fn sort_prompt_indices(&self) -> Vec<usize> {
622+
let mut prompt_indices = (0..self.prompts.len()).collect::<Vec<usize>>();
623+
624+
match self.sort_mode {
625+
SortMode::HistoryCount => {
626+
prompt_indices.sort_by(|&a, &b| {
627+
// First sort by history count (descending)
628+
let count_a = self.prompts[a].history_count();
629+
let count_b = self.prompts[b].history_count();
630+
631+
count_b.cmp(&count_a)
632+
});
633+
}
634+
SortMode::LastUsage => {
635+
prompt_indices.sort_by(|&a, &b| {
636+
// Sort by last usage time (descending)
637+
let last_used_a = self.prompts[a].get_last_used_time();
638+
let last_used_b = self.prompts[b].get_last_used_time();
639+
640+
// Handle cases where one or both might be None
641+
match (last_used_a, last_used_b) {
642+
(Some(time_a), Some(time_b)) => time_b.cmp(&time_a), // More recent first
643+
(Some(_), None) => std::cmp::Ordering::Less, // a is more recent
644+
(None, Some(_)) => std::cmp::Ordering::Greater, // b is more recent
645+
(None, None) => std::cmp::Ordering::Equal, // both are equal
646+
}
647+
});
648+
}
649+
SortMode::InsertionOrder => {
650+
// No sorting - maintain insertion order
651+
}
652+
}
653+
654+
prompt_indices
655+
}
656+
560657
fn show_left_panel_prompts(
561658
&mut self,
562659
ui: &mut egui::Ui,
@@ -566,7 +663,11 @@ impl App {
566663
let mut action = None;
567664

568665
ScrollArea::vertical().show(ui, |ui| {
569-
for (idx, prompt) in self.prompts.iter().enumerate() {
666+
// Sort prompts based on current sort mode
667+
let prompt_indices = self.sort_prompt_indices();
668+
669+
for &idx in &prompt_indices {
670+
let prompt = &self.prompts[idx];
570671
let selected = self.view.is_prompt_selected(idx);
571672

572673
ui.add_space(3.0);

src/prompt.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{collections::VecDeque, time::Instant};
22

3+
use egui::RichText;
34
use egui::{
45
Color32, CornerRadius, Frame, Key, KeyboardShortcut, Label, Layout, Modifiers, ScrollArea,
56
Sense, Stroke, UiBuilder,
@@ -47,6 +48,8 @@ struct PromptResponse {
4748
local_model_name: String,
4849
#[serde(skip)]
4950
requested_at: Instant,
51+
#[serde(skip)]
52+
created_at: Instant,
5053
}
5154

5255
impl Default for PromptResponse {
@@ -56,6 +59,7 @@ impl Default for PromptResponse {
5659
output: Default::default(),
5760
local_model_name: "unknown_model".to_owned(),
5861
requested_at: Instant::now(),
62+
created_at: Instant::now(),
5963
}
6064
}
6165
}
@@ -98,6 +102,18 @@ impl Prompt {
98102
self.history.remove(history_idx);
99103
}
100104

105+
pub fn history_count(&self) -> usize {
106+
self.history.len()
107+
}
108+
109+
pub fn get_last_used_time(&self) -> Option<Instant> {
110+
// Find the most recently created response in history
111+
self.history
112+
.iter()
113+
.map(|response| response.created_at)
114+
.max()
115+
}
116+
101117
pub fn show_left_panel(
102118
&self,
103119
ui: &mut egui::Ui,
@@ -152,6 +168,10 @@ impl Prompt {
152168
.stroke(Stroke::NONE),
153169
);
154170

171+
let count_text = format!("{:3}", self.history.len());
172+
173+
ui.add(egui::Label::new(RichText::new(count_text)));
174+
155175
if remove_response.on_hover_text("Remove prompt").clicked()
156176
{
157177
action = Some(AppAction::OpenRemovePromptDialog(idx));
@@ -336,15 +356,12 @@ impl Prompt {
336356

337357
if copy_response
338358
.on_hover_text("Copy response")
339-
.clicked()
340-
{
341-
if let Err(e) = crate::copy_to_clipboard(&prompt_response.output) {
359+
.clicked() && let Err(e) = crate::copy_to_clipboard(&prompt_response.output) {
342360
action = Some(AppAction::ShowErrorDialog {
343361
title: "Copy Error".to_string(),
344362
message: format!("Failed to copy to clipboard: {e}"),
345363
});
346-
}
347-
}
364+
};
348365
});
349366
},
350367
);
@@ -414,12 +431,14 @@ impl Prompt {
414431

415432
self.ask_flower
416433
.extract(|output| {
417-
self.history.get_mut(0).unwrap().output = output;
434+
let response = self.history.get_mut(0).unwrap();
435+
response.output = output;
418436
})
419437
.finalize(|result| {
420438
match result {
421439
Ok(output) => {
422-
self.history.get_mut(0).unwrap().output = output;
440+
let response = self.history.get_mut(0).unwrap();
441+
response.output = output;
423442
}
424443
Err(Compact::Suppose(e)) => {
425444
// Remove the failed response from history

0 commit comments

Comments
 (0)