@@ -54,7 +54,9 @@ use std::{
};
use theme::ThemeSettings;
use ui::{
- h_stack, prelude::*, v_stack, Button, ButtonLike, Icon, IconButton, IconElement, Label, Tooltip,
+ prelude::*,
+ utils::{DateTimeType, FormatDistance},
+ ButtonLike, Tab, TabBar, Tooltip,
};
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -939,7 +941,7 @@ impl AssistantPanel {
this.set_active_editor_index(this.prev_active_editor_index, cx);
}
}))
- .tooltip(|cx| Tooltip::text("History", cx))
+ .tooltip(|cx| Tooltip::text("Conversation History", cx))
}
fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
@@ -955,12 +957,13 @@ impl AssistantPanel {
}
fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
- IconButton::new("split_button", Icon::SplitMessage)
+ IconButton::new("split_button", Icon::Snip)
.on_click(cx.listener(|this, _event, cx| {
if let Some(active_editor) = this.active_editor() {
active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
}
}))
+ .icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::for_action("Split Message", &Split, cx))
}
@@ -971,6 +974,7 @@ impl AssistantPanel {
active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
}
}))
+ .icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::for_action("Assist", &Assist, cx))
}
@@ -985,6 +989,7 @@ impl AssistantPanel {
});
}
}))
+ .icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::for_action("Quote Seleciton", &QuoteSelection, cx))
}
@@ -993,15 +998,19 @@ impl AssistantPanel {
.on_click(cx.listener(|this, _event, cx| {
this.new_conversation(cx);
}))
+ .icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::for_action("New Conversation", &NewConversation, cx))
}
fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let zoomed = self.zoomed;
- IconButton::new("zoom_button", Icon::MagnifyingGlass)
+ IconButton::new("zoom_button", Icon::Maximize)
.on_click(cx.listener(|this, _event, cx| {
this.toggle_zoom(&ToggleZoom, cx);
}))
+ .selected(zoomed)
+ .selected_icon(Icon::Minimize)
+ .icon_size(IconSize::Small)
.tooltip(move |cx| {
Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
})
@@ -1020,10 +1029,19 @@ impl AssistantPanel {
this.open_conversation(path.clone(), cx)
.detach_and_log_err(cx)
}))
- .child(Label::new(
- conversation.mtime.format("%F %I:%M%p").to_string(),
- ))
- .child(Label::new(conversation.title.clone()))
+ .full_width()
+ .child(
+ div()
+ .flex()
+ .w_full()
+ .gap_2()
+ .child(
+ Label::new(conversation.mtime.format("%F %I:%M%p").to_string())
+ .color(Color::Muted)
+ .size(LabelSize::Small),
+ )
+ .child(Label::new(conversation.title.clone()).size(LabelSize::Small)),
+ )
}
fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
@@ -1112,20 +1130,35 @@ impl Render for AssistantPanel {
.border()
.border_color(gpui::red())
} else {
- let title = self
- .active_editor()
- .map(|editor| Label::new(editor.read(cx).title(cx)));
-
- let mut header = h_stack()
- .child(Self::render_hamburger_button(cx))
- .children(title);
-
- if self.focus_handle.contains_focused(cx) {
- header = header
- .children(self.render_editor_tools(cx))
- .child(Self::render_plus_button(cx))
- .child(self.render_zoom_button(cx));
- }
+ let header = TabBar::new("assistant_header")
+ .start_child(
+ h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title),
+ )
+ .children(self.active_editor().map(|editor| {
+ h_stack()
+ .h(rems(Tab::HEIGHT_IN_REMS))
+ .flex_1()
+ .px_2()
+ .child(Label::new(editor.read(cx).title(cx)).into_element())
+ }))
+ .end_child(if self.focus_handle.contains_focused(cx) {
+ h_stack()
+ .gap_2()
+ .child(h_stack().gap_1().children(self.render_editor_tools(cx)))
+ .child(
+ ui::Divider::vertical()
+ .inset()
+ .color(ui::DividerColor::Border),
+ )
+ .child(
+ h_stack()
+ .gap_1()
+ .child(Self::render_plus_button(cx))
+ .child(self.render_zoom_button(cx)),
+ )
+ } else {
+ div()
+ });
v_stack()
.size_full()
@@ -1165,8 +1198,6 @@ impl Render for AssistantPanel {
.into_any_element()
}),
)
- .border()
- .border_color(gpui::red())
}
}
}
@@ -2251,6 +2282,14 @@ impl ConversationEditor {
}
Role::System => Label::new("System").color(Color::Warning),
})
+ .tooltip(|cx| {
+ Tooltip::with_meta(
+ "Toggle message role",
+ None,
+ "Available roles: You (User), Assistant, System",
+ cx,
+ )
+ })
.on_click({
let conversation = conversation.clone();
move |_, cx| {
@@ -2265,10 +2304,22 @@ impl ConversationEditor {
h_stack()
.id(("message_header", message_id.0))
- .border()
- .border_color(gpui::red())
+ .h_11()
+ .gap_1()
+ .p_1()
.child(sender)
- .child(Label::new(message.sent_at.format("%I:%M%P").to_string()))
+ // TODO: Only show this if the message if the message has been sent
+ .child(
+ Label::new(
+ FormatDistance::from_now(DateTimeType::Local(
+ message.sent_at,
+ ))
+ .hide_prefix(true)
+ .add_suffix(true)
+ .to_string(),
+ )
+ .color(Color::Muted),
+ )
.children(
if let MessageStatus::Error(error) = message.status.clone() {
Some(
@@ -2429,6 +2480,7 @@ impl ConversationEditor {
"current_model",
self.conversation.read(cx).model.short_name(),
)
+ .style(ButtonStyle::Filled)
.tooltip(move |cx| Tooltip::text("Change Model", cx))
.on_click(cx.listener(|this, _, cx| this.cycle_model(cx)))
}
@@ -2442,12 +2494,7 @@ impl ConversationEditor {
} else {
Color::Default
};
- Some(
- div()
- .border()
- .border_color(gpui::red())
- .child(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color)),
- )
+ Some(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color))
}
}
@@ -2459,15 +2506,21 @@ impl Render for ConversationEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.key_context("ConversationEditor")
- .size_full()
- .relative()
.capture_action(cx.listener(ConversationEditor::cancel_last_assist))
.capture_action(cx.listener(ConversationEditor::save))
.capture_action(cx.listener(ConversationEditor::copy))
.capture_action(cx.listener(ConversationEditor::cycle_message_role))
.on_action(cx.listener(ConversationEditor::assist))
.on_action(cx.listener(ConversationEditor::split))
- .child(self.editor.clone())
+ .size_full()
+ .relative()
+ .child(
+ div()
+ .size_full()
+ .pl_2()
+ .bg(cx.theme().colors().editor_background)
+ .child(self.editor.clone()),
+ )
.child(
h_stack()
.absolute()
@@ -1,4 +1,72 @@
-use chrono::NaiveDateTime;
+use chrono::{DateTime, Local, NaiveDateTime};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DateTimeType {
+ Naive(NaiveDateTime),
+ Local(DateTime<Local>),
+}
+
+impl DateTimeType {
+ /// Converts the DateTimeType to a NaiveDateTime.
+ ///
+ /// If the DateTimeType is already a NaiveDateTime, it will be returned as is.
+ /// If the DateTimeType is a DateTime<Local>, it will be converted to a NaiveDateTime.
+ pub fn to_naive(&self) -> NaiveDateTime {
+ match self {
+ DateTimeType::Naive(naive) => *naive,
+ DateTimeType::Local(local) => local.naive_local(),
+ }
+ }
+}
+
+pub struct FormatDistance {
+ date: DateTimeType,
+ base_date: DateTimeType,
+ include_seconds: bool,
+ add_suffix: bool,
+ hide_prefix: bool,
+}
+
+impl FormatDistance {
+ pub fn new(date: DateTimeType, base_date: DateTimeType) -> Self {
+ Self {
+ date,
+ base_date,
+ include_seconds: false,
+ add_suffix: false,
+ hide_prefix: false,
+ }
+ }
+
+ pub fn from_now(date: DateTimeType) -> Self {
+ Self::new(date, DateTimeType::Local(Local::now()))
+ }
+
+ pub fn to_string(self) -> String {
+ format_distance(
+ self.date,
+ self.base_date.to_naive(),
+ self.include_seconds,
+ self.add_suffix,
+ self.hide_prefix,
+ )
+ }
+
+ pub fn include_seconds(mut self, include_seconds: bool) -> Self {
+ self.include_seconds = include_seconds;
+ self
+ }
+
+ pub fn add_suffix(mut self, add_suffix: bool) -> Self {
+ self.add_suffix = add_suffix;
+ self
+ }
+
+ pub fn hide_prefix(mut self, hide_prefix: bool) -> Self {
+ self.hide_prefix = hide_prefix;
+ self
+ }
+}
/// Calculates the distance in seconds between two NaiveDateTime objects.
/// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.
@@ -13,7 +81,12 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
}
/// Generates a string describing the time distance between two dates in a human-readable way.
-fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
+fn distance_string(
+ distance: i64,
+ include_seconds: bool,
+ add_suffix: bool,
+ hide_prefix: bool,
+) -> String {
let suffix = if distance < 0 { " from now" } else { " ago" };
let distance = distance.abs();
@@ -24,53 +97,128 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
let months = distance / 2_592_000;
let string = if distance < 5 && include_seconds {
- "less than 5 seconds".to_string()
+ if hide_prefix {
+ "5 seconds"
+ } else {
+ "less than 5 seconds"
+ }
+ .to_string()
} else if distance < 10 && include_seconds {
- "less than 10 seconds".to_string()
+ if hide_prefix {
+ "10 seconds"
+ } else {
+ "less than 10 seconds"
+ }
+ .to_string()
} else if distance < 20 && include_seconds {
- "less than 20 seconds".to_string()
+ if hide_prefix {
+ "20 seconds"
+ } else {
+ "less than 20 seconds"
+ }
+ .to_string()
} else if distance < 40 && include_seconds {
- "half a minute".to_string()
+ if hide_prefix {
+ "half a minute"
+ } else {
+ "half a minute"
+ }
+ .to_string()
} else if distance < 60 && include_seconds {
- "less than a minute".to_string()
+ if hide_prefix {
+ "a minute"
+ } else {
+ "less than a minute"
+ }
+ .to_string()
} else if distance < 90 && include_seconds {
"1 minute".to_string()
} else if distance < 30 {
- "less than a minute".to_string()
+ if hide_prefix {
+ "a minute"
+ } else {
+ "less than a minute"
+ }
+ .to_string()
} else if distance < 90 {
"1 minute".to_string()
} else if distance < 2_700 {
format!("{} minutes", minutes)
} else if distance < 5_400 {
- "about 1 hour".to_string()
+ if hide_prefix {
+ "1 hour"
+ } else {
+ "about 1 hour"
+ }
+ .to_string()
} else if distance < 86_400 {
- format!("about {} hours", hours)
+ if hide_prefix {
+ format!("{} hours", hours)
+ } else {
+ format!("about {} hours", hours)
+ }
+ .to_string()
} else if distance < 172_800 {
"1 day".to_string()
} else if distance < 2_592_000 {
format!("{} days", days)
} else if distance < 5_184_000 {
- "about 1 month".to_string()
+ if hide_prefix {
+ "1 month"
+ } else {
+ "about 1 month"
+ }
+ .to_string()
} else if distance < 7_776_000 {
- "about 2 months".to_string()
+ if hide_prefix {
+ "2 months"
+ } else {
+ "about 2 months"
+ }
+ .to_string()
} else if distance < 31_540_000 {
format!("{} months", months)
} else if distance < 39_425_000 {
- "about 1 year".to_string()
+ if hide_prefix {
+ "1 year"
+ } else {
+ "about 1 year"
+ }
+ .to_string()
} else if distance < 55_195_000 {
- "over 1 year".to_string()
+ if hide_prefix { "1 year" } else { "over 1 year" }.to_string()
} else if distance < 63_080_000 {
- "almost 2 years".to_string()
+ if hide_prefix {
+ "2 years"
+ } else {
+ "almost 2 years"
+ }
+ .to_string()
} else {
let years = distance / 31_536_000;
let remaining_months = (distance % 31_536_000) / 2_592_000;
if remaining_months < 3 {
- format!("about {} years", years)
+ if hide_prefix {
+ format!("{} years", years)
+ } else {
+ format!("about {} years", years)
+ }
+ .to_string()
} else if remaining_months < 9 {
- format!("over {} years", years)
+ if hide_prefix {
+ format!("{} years", years)
+ } else {
+ format!("over {} years", years)
+ }
+ .to_string()
} else {
- format!("almost {} years", years + 1)
+ if hide_prefix {
+ format!("{} years", years + 1)
+ } else {
+ format!("almost {} years", years + 1)
+ }
+ .to_string()
}
};
@@ -108,15 +256,16 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
/// ```
///
/// Output: `"There was about 3 years between the first and last crewed moon landings."`
-pub fn naive_format_distance(
- date: NaiveDateTime,
+pub fn format_distance(
+ date: DateTimeType,
base_date: NaiveDateTime,
include_seconds: bool,
add_suffix: bool,
+ hide_prefix: bool,
) -> String {
- let distance = distance_in_seconds(date, base_date);
+ let distance = distance_in_seconds(date.to_naive(), base_date);
- distance_string(distance, include_seconds, add_suffix)
+ distance_string(distance, include_seconds, add_suffix, hide_prefix)
}
/// Get the time difference between a date and now as relative human readable string.
@@ -142,14 +291,15 @@ pub fn naive_format_distance(
/// ```
///
/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.`
-pub fn naive_format_distance_from_now(
- datetime: NaiveDateTime,
+pub fn format_distance_from_now(
+ datetime: DateTimeType,
include_seconds: bool,
add_suffix: bool,
+ hide_prefix: bool,
) -> String {
let now = chrono::offset::Local::now().naive_local();
- naive_format_distance(datetime, now, include_seconds, add_suffix)
+ format_distance(datetime, now, include_seconds, add_suffix, hide_prefix)
}
#[cfg(test)]
@@ -158,73 +308,127 @@ mod tests {
use chrono::NaiveDateTime;
#[test]
- fn test_naive_format_distance() {
- let date =
- NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
- let base_date =
- NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
+ fn test_format_distance() {
+ let date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"),
+ );
+ let base_date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"),
+ );
assert_eq!(
"about 2 hours",
- naive_format_distance(date, base_date, false, false)
+ format_distance(date, base_date.to_naive(), false, false, false)
);
}
#[test]
- fn test_naive_format_distance_with_suffix() {
- let date =
- NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
- let base_date =
- NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
+ fn test_format_distance_with_suffix() {
+ let date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"),
+ );
+ let base_date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"),
+ );
assert_eq!(
"about 2 hours from now",
- naive_format_distance(date, base_date, false, true)
+ format_distance(date, base_date.to_naive(), false, true, false)
);
}
#[test]
- fn test_naive_format_distance_from_now() {
- let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
- .expect("Invalid NaiveDateTime for date");
+ fn test_format_distance_from_now() {
+ let date = DateTimeType::Naive(
+ NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
+ .expect("Invalid NaiveDateTime for date"),
+ );
assert_eq!(
"over 54 years ago",
- naive_format_distance_from_now(date, false, true)
+ format_distance_from_now(date, false, true, false)
);
}
#[test]
- fn test_naive_format_distance_string() {
- assert_eq!(distance_string(3, false, false), "less than a minute");
- assert_eq!(distance_string(7, false, false), "less than a minute");
- assert_eq!(distance_string(13, false, false), "less than a minute");
- assert_eq!(distance_string(21, false, false), "less than a minute");
- assert_eq!(distance_string(45, false, false), "1 minute");
- assert_eq!(distance_string(61, false, false), "1 minute");
- assert_eq!(distance_string(1920, false, false), "32 minutes");
- assert_eq!(distance_string(3902, false, false), "about 1 hour");
- assert_eq!(distance_string(18002, false, false), "about 5 hours");
- assert_eq!(distance_string(86470, false, false), "1 day");
- assert_eq!(distance_string(345880, false, false), "4 days");
- assert_eq!(distance_string(2764800, false, false), "about 1 month");
- assert_eq!(distance_string(5184000, false, false), "about 2 months");
- assert_eq!(distance_string(10368000, false, false), "4 months");
- assert_eq!(distance_string(34694000, false, false), "about 1 year");
- assert_eq!(distance_string(47310000, false, false), "over 1 year");
- assert_eq!(distance_string(61503000, false, false), "almost 2 years");
- assert_eq!(distance_string(160854000, false, false), "about 5 years");
- assert_eq!(distance_string(236550000, false, false), "over 7 years");
- assert_eq!(distance_string(249166000, false, false), "almost 8 years");
+ fn test_format_distance_string() {
+ assert_eq!(
+ distance_string(3, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(
+ distance_string(7, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(
+ distance_string(13, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(
+ distance_string(21, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(distance_string(45, false, false, false), "1 minute");
+ assert_eq!(distance_string(61, false, false, false), "1 minute");
+ assert_eq!(distance_string(1920, false, false, false), "32 minutes");
+ assert_eq!(distance_string(3902, false, false, false), "about 1 hour");
+ assert_eq!(distance_string(18002, false, false, false), "about 5 hours");
+ assert_eq!(distance_string(86470, false, false, false), "1 day");
+ assert_eq!(distance_string(345880, false, false, false), "4 days");
+ assert_eq!(
+ distance_string(2764800, false, false, false),
+ "about 1 month"
+ );
+ assert_eq!(
+ distance_string(5184000, false, false, false),
+ "about 2 months"
+ );
+ assert_eq!(distance_string(10368000, false, false, false), "4 months");
+ assert_eq!(
+ distance_string(34694000, false, false, false),
+ "about 1 year"
+ );
+ assert_eq!(
+ distance_string(47310000, false, false, false),
+ "over 1 year"
+ );
+ assert_eq!(
+ distance_string(61503000, false, false, false),
+ "almost 2 years"
+ );
+ assert_eq!(
+ distance_string(160854000, false, false, false),
+ "about 5 years"
+ );
+ assert_eq!(
+ distance_string(236550000, false, false, false),
+ "over 7 years"
+ );
+ assert_eq!(
+ distance_string(249166000, false, false, false),
+ "almost 8 years"
+ );
}
#[test]
- fn test_naive_format_distance_string_include_seconds() {
- assert_eq!(distance_string(3, true, false), "less than 5 seconds");
- assert_eq!(distance_string(7, true, false), "less than 10 seconds");
- assert_eq!(distance_string(13, true, false), "less than 20 seconds");
- assert_eq!(distance_string(21, true, false), "half a minute");
- assert_eq!(distance_string(45, true, false), "less than a minute");
- assert_eq!(distance_string(61, true, false), "1 minute");
+ fn test_format_distance_string_include_seconds() {
+ assert_eq!(
+ distance_string(3, true, false, false),
+ "less than 5 seconds"
+ );
+ assert_eq!(
+ distance_string(7, true, false, false),
+ "less than 10 seconds"
+ );
+ assert_eq!(
+ distance_string(13, true, false, false),
+ "less than 20 seconds"
+ );
+ assert_eq!(distance_string(21, true, false, false), "half a minute");
+ assert_eq!(
+ distance_string(45, true, false, false),
+ "less than a minute"
+ );
+ assert_eq!(distance_string(61, true, false, false), "1 minute");
}
}