Skip to main content

guise/ai/
thinking.rs

1//! `AIThinking` — the gap between sending and the first token.
2//!
3//! That gap can run to several seconds, and an unchanged screen during it
4//! reads as a hang. Three dots cycling is the smallest thing that says the
5//! request is alive without claiming to know how long it will take, which a
6//! progress bar would.
7
8use gpui::prelude::*;
9use gpui::{div, px, App, IntoElement, SharedString, Window};
10
11use crate::devtools::Probed;
12use crate::feedback::{Loader, LoaderVariant};
13use crate::theme::{theme, ColorName, Size};
14
15/// A "still working" indicator with an optional label.
16#[derive(IntoElement)]
17pub struct AIThinking {
18    label: Option<SharedString>,
19    size: Size,
20    color: Option<ColorName>,
21}
22
23impl AIThinking {
24    pub fn new() -> Self {
25        AIThinking {
26            label: None,
27            size: Size::Sm,
28            color: None,
29        }
30    }
31
32    /// Say what is happening — "Thinking", "Searching the web", "Running
33    /// tests". A specific label is worth far more than a generic one.
34    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
35        self.label = Some(label.into());
36        self
37    }
38
39    pub fn size(mut self, size: Size) -> Self {
40        self.size = size;
41        self
42    }
43
44    pub fn color(mut self, color: ColorName) -> Self {
45        self.color = Some(color);
46        self
47    }
48}
49
50impl Default for AIThinking {
51    fn default() -> Self {
52        AIThinking::new()
53    }
54}
55
56impl RenderOnce for AIThinking {
57    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
58        let t = theme(cx);
59        let font = t.font_size(self.size);
60        let dot_color = self
61            .color
62            .map_or_else(|| t.dimmed(), |name| t.color(name, 6))
63            .hsla();
64        let dimmed = t.dimmed().hsla();
65        div()
66            .flex()
67            .items_center()
68            .gap(px(8.0))
69            .text_size(px(font))
70            .text_color(dimmed)
71            .child(
72                Loader::new()
73                    .variant(LoaderVariant::Dots)
74                    .size(self.size)
75                    .color(dot_color),
76            )
77            .children(self.label)
78            .probe("AIThinking")
79    }
80}