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:
1 parent
4cbdd9322c
commit
471c7bc188
8 files changed
+794
-8
No files matched your search
+87
-1
@@ -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:?}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user