agent_ui: Add timestamp to thread item in the sidebar (#51327)
Release Notes: - N/A
This commit is contained in:
1 parent
b5aa635947
commit
d97d33b31f
2 files changed
+143
-23
No files matched your search
@@ -1180,11 +1180,37 @@ impl Sidebar {
|
||||
|
||||
let id = SharedString::from(format!("thread-entry-{}", ix));
|
||||
|
||||
let timestamp = thread
|
||||
.session_info
|
||||
.created_at
|
||||
.or(thread.session_info.updated_at)
|
||||
.map(|entry_time| {
|
||||
let now = Utc::now();
|
||||
let duration = now.signed_duration_since(entry_time);
|
||||
|
||||
let minutes = duration.num_minutes();
|
||||
let hours = duration.num_hours();
|
||||
let days = duration.num_days();
|
||||
let weeks = days / 7;
|
||||
let months = days / 30;
|
||||
|
||||
if minutes < 60 {
|
||||
format!("{}m", minutes.max(1))
|
||||
} else if hours < 24 {
|
||||
format!("{}h", hours)
|
||||
} else if weeks < 4 {
|
||||
format!("{}w", weeks.max(1))
|
||||
} else {
|
||||
format!("{}mo", months.max(1))
|
||||
}
|
||||
});
|
||||
|
||||
ThreadItem::new(id, title)
|
||||
.icon(thread.icon)
|
||||
.when_some(thread.icon_from_external_svg.clone(), |this, svg| {
|
||||
this.custom_icon_from_external_svg(svg)
|
||||
})
|
||||
.when_some(timestamp, |this, ts| this.timestamp(ts))
|
||||
.highlight_positions(thread.highlight_positions.to_vec())
|
||||
.status(thread.status)
|
||||
.notified(has_notification)
|
||||
|
||||
@@ -245,6 +245,8 @@ impl RenderOnce for ThreadItem {
|
||||
let removed_count = self.removed.unwrap_or(0);
|
||||
let diff_stat_id = self.id.clone();
|
||||
let has_worktree = self.worktree.is_some();
|
||||
let has_timestamp = !self.timestamp.is_empty();
|
||||
let timestamp = self.timestamp;
|
||||
|
||||
v_flex()
|
||||
.id(self.id.clone())
|
||||
@@ -253,13 +255,7 @@ impl RenderOnce for ThreadItem {
|
||||
.overflow_hidden()
|
||||
.cursor_pointer()
|
||||
.w_full()
|
||||
.map(|this| {
|
||||
if has_worktree || has_diff_stats {
|
||||
this.p_2()
|
||||
} else {
|
||||
this.p_1()
|
||||
}
|
||||
})
|
||||
.p_1()
|
||||
.when(self.selected, |s| s.bg(color.element_active))
|
||||
.border_1()
|
||||
.border_color(gpui::transparent_black())
|
||||
@@ -310,23 +306,47 @@ impl RenderOnce for ThreadItem {
|
||||
.gap_1p5()
|
||||
.child(icon_container()) // Icon Spacing
|
||||
.child(worktree_label)
|
||||
.child(dot_separator())
|
||||
.when(has_diff_stats || has_timestamp, |this| {
|
||||
this.child(dot_separator())
|
||||
})
|
||||
.when(has_diff_stats, |this| {
|
||||
this.child(DiffStat::new(
|
||||
diff_stat_id.clone(),
|
||||
added_count,
|
||||
removed_count,
|
||||
))
|
||||
})
|
||||
.when(has_diff_stats && has_timestamp, |this| {
|
||||
this.child(dot_separator())
|
||||
})
|
||||
.when(has_timestamp, |this| {
|
||||
this.child(
|
||||
Label::new(timestamp.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(!has_worktree && has_diff_stats, |this| {
|
||||
.when(!has_worktree && (has_diff_stats || has_timestamp), |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.min_w_0()
|
||||
.gap_1p5()
|
||||
.child(icon_container()) // Icon Spacing
|
||||
.child(DiffStat::new(diff_stat_id, added_count, removed_count)),
|
||||
.when(has_diff_stats, |this| {
|
||||
this.child(DiffStat::new(diff_stat_id, added_count, removed_count))
|
||||
})
|
||||
.when(has_diff_stats && has_timestamp, |this| {
|
||||
this.child(dot_separator())
|
||||
})
|
||||
.when(has_timestamp, |this| {
|
||||
this.child(
|
||||
Label::new(timestamp.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(self.on_click, |this, on_click| this.on_click(on_click))
|
||||
@@ -349,21 +369,31 @@ impl Component for ThreadItem {
|
||||
|
||||
let thread_item_examples = vec![
|
||||
single_example(
|
||||
"Default",
|
||||
"Default (minutes)",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-1", "Linking to the Agent Panel Depending on Settings")
|
||||
.icon(IconName::AiOpenAi)
|
||||
.timestamp("1:33 AM"),
|
||||
.timestamp("15m"),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Notified",
|
||||
"Timestamp Only (hours)",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-1b", "Thread with just a timestamp")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("3h"),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Notified (weeks)",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-2", "Refine thread view scrolling behavior")
|
||||
.timestamp("12:12 AM")
|
||||
.timestamp("1w")
|
||||
.notified(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
@@ -373,7 +403,7 @@ impl Component for ThreadItem {
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-2b", "Execute shell command in terminal")
|
||||
.timestamp("12:15 AM")
|
||||
.timestamp("2h")
|
||||
.status(AgentThreadStatus::WaitingForConfirmation),
|
||||
)
|
||||
.into_any_element(),
|
||||
@@ -383,7 +413,7 @@ impl Component for ThreadItem {
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-2c", "Failed to connect to language server")
|
||||
.timestamp("12:20 AM")
|
||||
.timestamp("5h")
|
||||
.status(AgentThreadStatus::Error),
|
||||
)
|
||||
.into_any_element(),
|
||||
@@ -394,7 +424,7 @@ impl Component for ThreadItem {
|
||||
.child(
|
||||
ThreadItem::new("ti-3", "Add line numbers option to FileEditBlock")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("7:30 PM")
|
||||
.timestamp("23h")
|
||||
.status(AgentThreadStatus::Running),
|
||||
)
|
||||
.into_any_element(),
|
||||
@@ -405,30 +435,43 @@ impl Component for ThreadItem {
|
||||
.child(
|
||||
ThreadItem::new("ti-4", "Add line numbers option to FileEditBlock")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("7:37 PM")
|
||||
.timestamp("2w")
|
||||
.worktree("link-agent-panel"),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Changes",
|
||||
"With Changes (months)",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-5", "Managing user and project settings interactions")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("7:37 PM")
|
||||
.timestamp("1mo")
|
||||
.added(10)
|
||||
.removed(3),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Worktree + Changes + Timestamp",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-5b", "Full metadata example")
|
||||
.icon(IconName::AiClaude)
|
||||
.worktree("my-project")
|
||||
.added(42)
|
||||
.removed(17)
|
||||
.timestamp("3w"),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected Item",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-6", "Refine textarea interaction behavior")
|
||||
.icon(IconName::AiGemini)
|
||||
.timestamp("3:00 PM")
|
||||
.timestamp("45m")
|
||||
.selected(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
@@ -439,23 +482,74 @@ impl Component for ThreadItem {
|
||||
.child(
|
||||
ThreadItem::new("ti-7", "Implement keyboard navigation")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("4:00 PM")
|
||||
.timestamp("12h")
|
||||
.focused(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Focused + Docked Right",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-7b", "Focused with right dock border")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("1w")
|
||||
.focused(true)
|
||||
.docked_right(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected + Focused",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-8", "Active and keyboard-focused thread")
|
||||
.icon(IconName::AiGemini)
|
||||
.timestamp("5:00 PM")
|
||||
.timestamp("2mo")
|
||||
.selected(true)
|
||||
.focused(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Hovered with Action Slot",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-9", "Hover to see action button")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("6h")
|
||||
.hovered(true)
|
||||
.action_slot(
|
||||
IconButton::new("delete", IconName::Trash)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Search Highlight",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-10", "Implement keyboard navigation")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("4w")
|
||||
.highlight_positions(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Worktree Search Highlight",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-11", "Search in worktree name")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("3mo")
|
||||
.worktree("my-project-name")
|
||||
.worktree_highlight_positions(vec![3, 4, 5, 6, 7, 8, 9, 10, 11]),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
];
|
||||
|
||||
Some(
|
||||
|
||||
Reference in new issue
Block a user