T23b: AI chat, citations

- CitationBadge: a numbered pill after a claim, its source on hover, a press
  opens it; a Tab stop
- SourceCard and SourceList: the sources folded under their count and sites,
  each a card numbered as cited; the fold opens from the keyboard
- SearchProgress: steps waiting, working and done
- WebResultCard and DocumentChunkPreview with matched words washed
- gallery: citation and sources states
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 06:07:42 -04:00
1 parent 4cbdd9322c
commit 471c7bc188
8 files changed
+794 -8

No files matched your search

+87 -1
View File
@@ -8,7 +8,8 @@ use gpui::{
};
use super::{
ImageMessage, MessageAvatar, MessageBubble, MessageEditor, MessageList, Role, ThinkingBlock,
CitationBadge, ImageMessage, MessageAvatar, MessageBubble, MessageEditor, MessageList, Role,
Source, SourceList, ThinkingBlock,
};
use crate::{forms, forms::TextInput, theme::Theme};
@@ -356,3 +357,88 @@ fn the_reasoning_opens_from_the_keyboard(cx: &mut TestAppContext) {
"Tab and Space open it: {closed:?} to {open:?}"
);
}
fn source(title: &str) -> Source {
Source {
site: "example.com".into(),
title: title.to_string().into(),
url: "https://example.com".into(),
snippet: None,
}
}
/// Sources folded under their count, the list's box measured, and how often one opened.
struct Cited {
frame: Measured,
opened: usize,
}
impl Render for Cited {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (frame, me) = (self.frame.clone(), cx.entity());
div()
.size_full()
.child(
CitationBadge::new("cite", 1, source("Gamma"))
.on_open(move |_, cx| me.update(cx, |cited, _| cited.opened += 1)),
)
.child(
div()
.w(px(320.0))
.relative()
.child(SourceList::new(
"sources",
[source("Gamma"), source("Lift")],
))
.child(
canvas(
move |bounds, _, _| *frame.borrow_mut() = Some(bounds),
|_, _, _, _| {},
)
.absolute()
.top_0()
.left_0()
.size_full(),
),
)
}
}
fn release(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse(key).expect("the key parses"),
});
settle(cx);
}
#[gpui::test]
fn citations_and_sources_open_from_the_keyboard(cx: &mut TestAppContext) {
cx.update(|cx| {
Theme::init(cx);
forms::bind_keys(cx);
});
let frame = Measured::default();
let seen = frame.clone();
let (cited, cx) = cx.add_window_view(move |_, _| Cited {
frame: seen,
opened: 0,
});
cx.update(|window, _| window.activate_window());
settle(cx);
cx.update(|window, _| window.focus_next());
release("enter", cx);
assert_eq!(
cited.read_with(cx, |cited, _| cited.opened),
1,
"the badge opens its source"
);
let folded = frame.borrow().expect("the list is laid out").size.height;
cx.update(|window, _| window.focus_next());
release("space", cx);
let open = frame.borrow().expect("the list is laid out").size.height;
assert!(
open > folded,
"Tab and Space open the sources: {folded:?} to {open:?}"
);
}