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}