gpui-ai 0.8.0

AI-native UI components for GPUI applications, built on gpui-component.
Documentation
use gpui::{
    Context, Element as _, IntoElement as _, Render, RenderOnce as _, Role, TestAppContext, Window,
    accesskit, canvas,
};
use gpui_ai::context_meter::{ContextMeter, ContextMeterVariant, ContextUsage};
use std::sync::{Arc, Mutex};

struct CapturedNode {
    role: Option<Role>,
    node: accesskit::Node,
}

struct Probe {
    variant: ContextMeterVariant,
    captured: Arc<Mutex<Option<CapturedNode>>>,
}

impl Render for Probe {
    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl gpui::IntoElement {
        let variant = self.variant;
        let captured = self.captured.clone();
        canvas(
            move |_, window, cx| {
                let usage = ContextUsage::new(84_300, 200_000);
                let element = ContextMeter::new("context", &usage)
                    .variant(variant)
                    .render(window, cx)
                    .into_element();
                let role = element.a11y_role();
                let mut node = accesskit::Node::new(Role::Unknown);
                element.write_a11y_info(&mut node);
                *captured.lock().expect("capture mutex should be available") =
                    Some(CapturedNode { role, node });
            },
            |_, _, _, _| {},
        )
    }
}

fn capture(variant: ContextMeterVariant, cx: &mut TestAppContext) -> CapturedNode {
    cx.update(gpui_ai::init);
    let captured = Arc::new(Mutex::new(None));
    let result = captured.clone();
    let (_, cx) = cx.add_window_view(move |_, _| Probe { variant, captured });
    cx.update(|window, cx| window.draw(cx).clear(cx));
    result
        .lock()
        .expect("capture mutex should be available")
        .take()
        .expect("meter node should be captured")
}

#[gpui::test]
fn every_variant_is_a_named_progress_indicator_with_spoken_numbers(cx: &mut TestAppContext) {
    for variant in [
        ContextMeterVariant::Ring,
        ContextMeterVariant::Bar,
        ContextMeterVariant::Text,
    ] {
        let captured = capture(variant, cx);
        assert_eq!(captured.role, Some(Role::ProgressIndicator));
        assert_eq!(captured.node.label(), Some("Context usage"));
        assert_eq!(
            captured.node.description(),
            Some("84.3K of 200K tokens used, 42%")
        );
        assert_eq!(captured.node.numeric_value(), Some(42.0));
        assert_eq!(captured.node.min_numeric_value(), Some(0.0));
        assert_eq!(captured.node.max_numeric_value(), Some(100.0));
    }
}