Detailed changes
@@ -84,12 +84,11 @@ use crate::{
Agent, AgentDiffPane, AgentInitialContent, AgentPanel, AllowAlways, AllowOnce,
AuthorizeToolCall, ClearMessageQueue, CycleFavoriteModels, CycleModeSelector,
CycleThinkingEffort, EditFirstQueuedMessage, ExpandMessageEditor, Follow, KeepAll, NewThread,
- OpenAddContextMenu, OpenAgentDiff, OpenHistory, RejectAll, RejectOnce,
- RemoveFirstQueuedMessage, ScrollOutputLineDown, ScrollOutputLineUp, ScrollOutputPageDown,
- ScrollOutputPageUp, ScrollOutputToBottom, ScrollOutputToNextMessage,
- ScrollOutputToPreviousMessage, ScrollOutputToTop, SendImmediately, SendNextQueuedMessage,
- ToggleFastMode, ToggleProfileSelector, ToggleThinkingEffortMenu, ToggleThinkingMode,
- UndoLastReject,
+ OpenAddContextMenu, OpenAgentDiff, RejectAll, RejectOnce, RemoveFirstQueuedMessage,
+ ScrollOutputLineDown, ScrollOutputLineUp, ScrollOutputPageDown, ScrollOutputPageUp,
+ ScrollOutputToBottom, ScrollOutputToNextMessage, ScrollOutputToPreviousMessage,
+ ScrollOutputToTop, SendImmediately, SendNextQueuedMessage, ToggleFastMode,
+ ToggleProfileSelector, ToggleThinkingEffortMenu, ToggleThinkingMode, UndoLastReject,
};
const STOPWATCH_THRESHOLD: Duration = Duration::from_secs(30);
@@ -2194,65 +2194,73 @@ impl ThreadView {
let edits_expanded = self.edits_expanded;
let queue_expanded = self.queue_expanded;
- v_flex()
- .mx_2()
- .bg(self.activity_bar_bg(cx))
- .border_1()
- .border_b_0()
- .border_color(cx.theme().colors().border)
- .rounded_t_md()
- .shadow(vec![gpui::BoxShadow {
- color: gpui::black().opacity(0.12),
- offset: point(px(1.), px(-1.)),
- blur_radius: px(2.),
- spread_radius: px(0.),
- }])
- .when_some(subagents_awaiting_permission, |this, element| {
- this.child(element)
- })
- .when(
- has_subagents_awaiting
- && (!plan.is_empty() || !changed_buffers.is_empty() || !queue_is_empty),
- |this| this.child(Divider::horizontal().color(DividerColor::Border)),
- )
- .when(!plan.is_empty(), |this| {
- this.child(self.render_plan_summary(plan, window, cx))
- .when(plan_expanded, |parent| {
- parent.child(self.render_plan_entries(plan, window, cx))
+ let max_content_width = AgentSettings::get_global(cx).max_content_width;
+
+ div()
+ .w_full()
+ .max_w(max_content_width)
+ .mx_auto()
+ .child(
+ v_flex()
+ .mx_2()
+ .bg(self.activity_bar_bg(cx))
+ .border_1()
+ .border_b_0()
+ .border_color(cx.theme().colors().border)
+ .rounded_t_md()
+ .shadow(vec![gpui::BoxShadow {
+ color: gpui::black().opacity(0.12),
+ offset: point(px(1.), px(-1.)),
+ blur_radius: px(2.),
+ spread_radius: px(0.),
+ }])
+ .when_some(subagents_awaiting_permission, |this, element| {
+ this.child(element)
})
- })
- .when(!plan.is_empty() && !changed_buffers.is_empty(), |this| {
- this.child(Divider::horizontal().color(DividerColor::Border))
- })
- .when(
- !changed_buffers.is_empty() && thread.parent_session_id().is_none(),
- |this| {
- this.child(self.render_edits_summary(
- &changed_buffers,
- edits_expanded,
- pending_edits,
- cx,
- ))
- .when(edits_expanded, |parent| {
- parent.child(self.render_edited_files(
- action_log,
- telemetry.clone(),
- &changed_buffers,
- pending_edits,
- cx,
- ))
+ .when(
+ has_subagents_awaiting
+ && (!plan.is_empty() || !changed_buffers.is_empty() || !queue_is_empty),
+ |this| this.child(Divider::horizontal().color(DividerColor::Border)),
+ )
+ .when(!plan.is_empty(), |this| {
+ this.child(self.render_plan_summary(plan, window, cx))
+ .when(plan_expanded, |parent| {
+ parent.child(self.render_plan_entries(plan, window, cx))
+ })
})
- },
+ .when(!plan.is_empty() && !changed_buffers.is_empty(), |this| {
+ this.child(Divider::horizontal().color(DividerColor::Border))
+ })
+ .when(
+ !changed_buffers.is_empty() && thread.parent_session_id().is_none(),
+ |this| {
+ this.child(self.render_edits_summary(
+ &changed_buffers,
+ edits_expanded,
+ pending_edits,
+ cx,
+ ))
+ .when(edits_expanded, |parent| {
+ parent.child(self.render_edited_files(
+ action_log,
+ telemetry.clone(),
+ &changed_buffers,
+ pending_edits,
+ cx,
+ ))
+ })
+ },
+ )
+ .when(!queue_is_empty, |this| {
+ this.when(!plan.is_empty() || !changed_buffers.is_empty(), |this| {
+ this.child(Divider::horizontal().color(DividerColor::Border))
+ })
+ .child(self.render_message_queue_summary(window, cx))
+ .when(queue_expanded, |parent| {
+ parent.child(self.render_message_queue_entries(window, cx))
+ })
+ }),
)
- .when(!queue_is_empty, |this| {
- this.when(!plan.is_empty() || !changed_buffers.is_empty(), |this| {
- this.child(Divider::horizontal().color(DividerColor::Border))
- })
- .child(self.render_message_queue_summary(window, cx))
- .when(queue_expanded, |parent| {
- parent.child(self.render_message_queue_entries(window, cx))
- })
- })
.into_any()
.into()
}
@@ -8257,18 +8265,12 @@ impl ThreadView {
fn render_resume_notice(_cx: &Context<Self>) -> AnyElement {
let description = "This agent does not support viewing previous messages. However, your session will still continue from where you last left off.";
- div()
- .px_2()
- .pt_2()
- .pb_3()
- .w_full()
- .child(
- Callout::new()
- .severity(Severity::Info)
- .icon(IconName::Info)
- .title("Resumed Session")
- .description(description),
- )
+ Callout::new()
+ .border_position(ui::BorderPosition::Bottom)
+ .severity(Severity::Info)
+ .icon(IconName::Info)
+ .title("Resumed Session")
+ .description(description)
.into_any_element()
}
@@ -8282,96 +8284,6 @@ impl ThreadView {
cx.notify();
}
- fn render_empty_state_section_header(
- &self,
- label: impl Into<SharedString>,
- action_slot: Option<AnyElement>,
- cx: &mut Context<Self>,
- ) -> impl IntoElement {
- div().pl_1().pr_1p5().child(
- h_flex()
- .mt_2()
- .pl_1p5()
- .pb_1()
- .w_full()
- .justify_between()
- .border_b_1()
- .border_color(cx.theme().colors().border_variant)
- .child(
- Label::new(label.into())
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .children(action_slot),
- )
- }
-
- fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
- let render_history = !self.recent_history_entries.is_empty();
-
- v_flex()
- .size_full()
- .when(render_history, |this| {
- let recent_history = self.recent_history_entries.clone();
- this.justify_end().child(
- v_flex()
- .child(
- self.render_empty_state_section_header(
- "Recent",
- Some(
- Button::new("view-history", "View All")
- .style(ButtonStyle::Subtle)
- .label_size(LabelSize::Small)
- .key_binding(
- KeyBinding::for_action_in(
- &OpenHistory,
- &self.focus_handle(cx),
- cx,
- )
- .map(|kb| kb.size(rems_from_px(12.))),
- )
- .on_click(move |_event, window, cx| {
- window.dispatch_action(OpenHistory.boxed_clone(), cx);
- })
- .into_any_element(),
- ),
- cx,
- ),
- )
- .child(v_flex().p_1().pr_1p5().gap_1().children({
- let supports_delete = self
- .history
- .as_ref()
- .map_or(false, |h| h.read(cx).supports_delete());
- recent_history
- .into_iter()
- .enumerate()
- .map(move |(index, entry)| {
- // TODO: Add keyboard navigation.
- let is_hovered =
- self.hovered_recent_history_item == Some(index);
- crate::thread_history_view::HistoryEntryElement::new(
- entry,
- self.server_view.clone(),
- )
- .hovered(is_hovered)
- .supports_delete(supports_delete)
- .on_hover(cx.listener(move |this, is_hovered, _window, cx| {
- if *is_hovered {
- this.hovered_recent_history_item = Some(index);
- } else if this.hovered_recent_history_item == Some(index) {
- this.hovered_recent_history_item = None;
- }
- cx.notify();
- }))
- .into_any_element()
- })
- })),
- )
- })
- .into_any()
- }
-
fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Callout {
Callout::new()
.icon(IconName::Warning)
@@ -8625,27 +8537,28 @@ impl ThreadView {
impl Render for ThreadView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_messages = self.list_state.item_count() > 0;
- let v2_empty_state = !has_messages;
-
let max_content_width = AgentSettings::get_global(cx).max_content_width;
+ let list_state = self.list_state.clone();
let conversation = v_flex()
- .mx_auto()
- .max_w(max_content_width)
- .when(!v2_empty_state, |this| this.flex_1().size_full())
+ .when(self.resumed_without_history, |this| {
+ this.child(Self::render_resume_notice(cx))
+ })
.map(|this| {
- let this = this.when(self.resumed_without_history, |this| {
- this.child(Self::render_resume_notice(cx))
- });
if has_messages {
- let list_state = self.list_state.clone();
- this.child(self.render_entries(cx))
+ this.flex_1()
+ .size_full()
+ .child(
+ v_flex()
+ .mx_auto()
+ .max_w(max_content_width)
+ .size_full()
+ .child(self.render_entries(cx)),
+ )
.vertical_scrollbar_for(&list_state, window, cx)
.into_any()
- } else if v2_empty_state {
- this.into_any()
} else {
- this.child(self.render_recent_history(cx)).into_any()
+ this.into_any()
}
});
@@ -1,21 +1,19 @@
use crate::thread_history::ThreadHistory;
-use crate::{
- AgentPanel, ConversationView, DEFAULT_THREAD_TITLE, RemoveHistory, RemoveSelectedThread,
-};
+use crate::{DEFAULT_THREAD_TITLE, RemoveHistory, RemoveSelectedThread};
use acp_thread::AgentSessionInfo;
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta, Utc};
use editor::{Editor, EditorEvent};
use fuzzy::StringMatchCandidate;
use gpui::{
AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, ScrollStrategy, Task,
- UniformListScrollHandle, WeakEntity, Window, uniform_list,
+ UniformListScrollHandle, Window, uniform_list,
};
use std::{fmt::Display, ops::Range};
use text::Bias;
use time::{OffsetDateTime, UtcOffset};
use ui::{
- ElementId, HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip,
- WithScrollbar, prelude::*,
+ HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip, WithScrollbar,
+ prelude::*,
};
pub(crate) fn thread_title(entry: &AgentSessionInfo) -> SharedString {
@@ -637,139 +635,6 @@ impl Render for ThreadHistoryView {
}
}
-#[derive(IntoElement)]
-pub struct HistoryEntryElement {
- entry: AgentSessionInfo,
- conversation_view: WeakEntity<ConversationView>,
- selected: bool,
- hovered: bool,
- supports_delete: bool,
- on_hover: Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>,
-}
-
-impl HistoryEntryElement {
- pub fn new(entry: AgentSessionInfo, conversation_view: WeakEntity<ConversationView>) -> Self {
- Self {
- entry,
- conversation_view,
- selected: false,
- hovered: false,
- supports_delete: false,
- on_hover: Box::new(|_, _, _| {}),
- }
- }
-
- pub fn supports_delete(mut self, supports_delete: bool) -> Self {
- self.supports_delete = supports_delete;
- self
- }
-
- pub fn hovered(mut self, hovered: bool) -> Self {
- self.hovered = hovered;
- self
- }
-
- pub fn on_hover(mut self, on_hover: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
- self.on_hover = Box::new(on_hover);
- self
- }
-}
-
-impl RenderOnce for HistoryEntryElement {
- fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
- let id = ElementId::Name(self.entry.session_id.0.clone().into());
- let title = thread_title(&self.entry);
- let formatted_time = self
- .entry
- .updated_at
- .map(|timestamp| {
- let now = chrono::Utc::now();
- let duration = now.signed_duration_since(timestamp);
-
- if duration.num_days() > 0 {
- format!("{}d", duration.num_days())
- } else if duration.num_hours() > 0 {
- format!("{}h ago", duration.num_hours())
- } else if duration.num_minutes() > 0 {
- format!("{}m ago", duration.num_minutes())
- } else {
- "Just now".to_string()
- }
- })
- .unwrap_or_else(|| "Unknown".to_string());
-
- ListItem::new(id)
- .rounded()
- .toggle_state(self.selected)
- .spacing(ListItemSpacing::Sparse)
- .start_slot(
- h_flex()
- .w_full()
- .gap_2()
- .justify_between()
- .child(Label::new(title).size(LabelSize::Small).truncate())
- .child(
- Label::new(formatted_time)
- .color(Color::Muted)
- .size(LabelSize::XSmall),
- ),
- )
- .on_hover(self.on_hover)
- .end_slot::<IconButton>(if (self.hovered || self.selected) && self.supports_delete {
- Some(
- IconButton::new("delete", IconName::Trash)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .tooltip(move |_window, cx| {
- Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
- })
- .on_click({
- let conversation_view = self.conversation_view.clone();
- let session_id = self.entry.session_id.clone();
-
- move |_event, _window, cx| {
- if let Some(conversation_view) = conversation_view.upgrade() {
- conversation_view.update(cx, |conversation_view, cx| {
- conversation_view.delete_history_entry(&session_id, cx);
- });
- }
- }
- }),
- )
- } else {
- None
- })
- .on_click({
- let conversation_view = self.conversation_view.clone();
- let entry = self.entry;
-
- move |_event, window, cx| {
- if let Some(workspace) = conversation_view
- .upgrade()
- .and_then(|view| view.read(cx).workspace().upgrade())
- {
- if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
- panel.update(cx, |panel, cx| {
- if let Some(agent) = panel.selected_agent() {
- panel.load_agent_thread(
- agent,
- entry.session_id.clone(),
- entry.work_dirs.clone(),
- entry.title.clone(),
- true,
- window,
- cx,
- );
- }
- });
- }
- }
- }
- })
- }
-}
-
#[derive(Clone, Copy)]
pub enum EntryTimeFormat {
DateAndTime,