thread_history.rs

  1use gpui::{
  2    uniform_list, AppContext, FocusHandle, FocusableView, Model, UniformListScrollHandle, WeakView,
  3};
  4use time::{OffsetDateTime, UtcOffset};
  5use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
  6
  7use crate::thread::Thread;
  8use crate::thread_store::ThreadStore;
  9use crate::AssistantPanel;
 10
 11pub struct ThreadHistory {
 12    focus_handle: FocusHandle,
 13    assistant_panel: WeakView<AssistantPanel>,
 14    thread_store: Model<ThreadStore>,
 15    scroll_handle: UniformListScrollHandle,
 16}
 17
 18impl ThreadHistory {
 19    pub(crate) fn new(
 20        assistant_panel: WeakView<AssistantPanel>,
 21        thread_store: Model<ThreadStore>,
 22        cx: &mut ViewContext<Self>,
 23    ) -> Self {
 24        Self {
 25            focus_handle: cx.focus_handle(),
 26            assistant_panel,
 27            thread_store,
 28            scroll_handle: UniformListScrollHandle::default(),
 29        }
 30    }
 31}
 32
 33impl FocusableView for ThreadHistory {
 34    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
 35        self.focus_handle.clone()
 36    }
 37}
 38
 39impl Render for ThreadHistory {
 40    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 41        let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
 42
 43        v_flex()
 44            .id("thread-history-container")
 45            .track_focus(&self.focus_handle)
 46            .overflow_y_scroll()
 47            .size_full()
 48            .p_1()
 49            .map(|history| {
 50                if threads.is_empty() {
 51                    history
 52                        .justify_center()
 53                        .child(
 54                            h_flex().w_full().justify_center().child(
 55                                Label::new("You don't have any past threads yet.")
 56                                    .size(LabelSize::Small),
 57                            ),
 58                        )
 59                } else {
 60                    history.child(
 61                        uniform_list(
 62                            cx.view().clone(),
 63                            "thread-history",
 64                            threads.len(),
 65                            move |history, range, _cx| {
 66                                threads[range]
 67                                    .iter()
 68                                    .map(|thread| {
 69                                        h_flex().w_full().pb_1().child(PastThread::new(
 70                                            thread.clone(),
 71                                            history.assistant_panel.clone(),
 72                                        ))
 73                                    })
 74                                    .collect()
 75                            },
 76                        )
 77                        .track_scroll(self.scroll_handle.clone())
 78                        .flex_grow(),
 79                    )
 80                }
 81            })
 82    }
 83}
 84
 85#[derive(IntoElement)]
 86pub struct PastThread {
 87    thread: Model<Thread>,
 88    assistant_panel: WeakView<AssistantPanel>,
 89}
 90
 91impl PastThread {
 92    pub fn new(thread: Model<Thread>, assistant_panel: WeakView<AssistantPanel>) -> Self {
 93        Self {
 94            thread,
 95            assistant_panel,
 96        }
 97    }
 98}
 99
100impl RenderOnce for PastThread {
101    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
102        let (id, summary) = {
103            const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
104            let thread = self.thread.read(cx);
105            (
106                thread.id().clone(),
107                thread.summary().unwrap_or(DEFAULT_SUMMARY),
108            )
109        };
110
111        let thread_timestamp = time_format::format_localized_timestamp(
112            OffsetDateTime::from_unix_timestamp(self.thread.read(cx).updated_at().timestamp())
113                .unwrap(),
114            OffsetDateTime::now_utc(),
115            self.assistant_panel
116                .update(cx, |this, _cx| this.local_timezone())
117                .unwrap_or(UtcOffset::UTC),
118            time_format::TimestampFormat::EnhancedAbsolute,
119        );
120
121        ListItem::new(("past-thread", self.thread.entity_id()))
122            .outlined()
123            .start_slot(
124                Icon::new(IconName::MessageCircle)
125                    .size(IconSize::Small)
126                    .color(Color::Muted),
127            )
128            .spacing(ListItemSpacing::Sparse)
129            .child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
130            .end_slot(
131                h_flex()
132                    .gap_2()
133                    .child(
134                        Label::new(thread_timestamp)
135                            .color(Color::Disabled)
136                            .size(LabelSize::Small),
137                    )
138                    .child(
139                        IconButton::new("delete", IconName::TrashAlt)
140                            .shape(IconButtonShape::Square)
141                            .icon_size(IconSize::Small)
142                            .tooltip(|cx| Tooltip::text("Delete Thread", cx))
143                            .on_click({
144                                let assistant_panel = self.assistant_panel.clone();
145                                let id = id.clone();
146                                move |_event, cx| {
147                                    assistant_panel
148                                        .update(cx, |this, cx| {
149                                            this.delete_thread(&id, cx);
150                                        })
151                                        .ok();
152                                }
153                            }),
154                    ),
155            )
156            .on_click({
157                let assistant_panel = self.assistant_panel.clone();
158                let id = id.clone();
159                move |_event, cx| {
160                    assistant_panel
161                        .update(cx, |this, cx| {
162                            this.open_thread(&id, cx);
163                        })
164                        .ok();
165                }
166            })
167    }
168}