guise-ui 1.2.0

A component library for gpui, Zed's GPU-accelerated UI framework: a themed palette, sizing tokens, 130+ composable components, a reactive state layer, and an in-app Safari-style inspector.
Documentation
//! `AIThinking` — the gap between sending and the first token.
//!
//! That gap can run to several seconds, and an unchanged screen during it
//! reads as a hang. Three dots cycling is the smallest thing that says the
//! request is alive without claiming to know how long it will take, which a
//! progress bar would.

use gpui::prelude::*;
use gpui::{div, px, App, IntoElement, SharedString, Window};

use crate::devtools::Probed;
use crate::feedback::{Loader, LoaderVariant};
use crate::theme::{theme, ColorName, Size};

/// A "still working" indicator with an optional label.
#[derive(IntoElement)]
pub struct AIThinking {
    label: Option<SharedString>,
    size: Size,
    color: Option<ColorName>,
}

impl AIThinking {
    pub fn new() -> Self {
        AIThinking {
            label: None,
            size: Size::Sm,
            color: None,
        }
    }

    /// Say what is happening — "Thinking", "Searching the web", "Running
    /// tests". A specific label is worth far more than a generic one.
    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
        self.label = Some(label.into());
        self
    }

    pub fn size(mut self, size: Size) -> Self {
        self.size = size;
        self
    }

    pub fn color(mut self, color: ColorName) -> Self {
        self.color = Some(color);
        self
    }
}

impl Default for AIThinking {
    fn default() -> Self {
        AIThinking::new()
    }
}

impl RenderOnce for AIThinking {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let t = theme(cx);
        let font = t.font_size(self.size);
        let dot_color = self
            .color
            .map_or_else(|| t.dimmed(), |name| t.color(name, 6))
            .hsla();
        let dimmed = t.dimmed().hsla();
        div()
            .flex()
            .items_center()
            .gap(px(8.0))
            .text_size(px(font))
            .text_color(dimmed)
            .child(
                Loader::new()
                    .variant(LoaderVariant::Dots)
                    .size(self.size)
                    .color(dot_color),
            )
            .children(self.label)
            .probe("AIThinking")
    }
}