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:
8 files changed
+794
-8
No files matched your search
@@ -0,0 +1,123 @@
|
||||
use ely_gpui_component::chat::{
|
||||
CitationBadge, DocumentChunkPreview, SearchProgress, Source, SourceList, StepState,
|
||||
WebResultCard,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, Styled, Window, div, px};
|
||||
|
||||
use crate::{probe::probe, ui::section};
|
||||
|
||||
fn sources() -> Vec<Source> {
|
||||
[
|
||||
(
|
||||
"example.com",
|
||||
"Color, in light and dark",
|
||||
"https://example.com/color/light-and-dark",
|
||||
"How one accent reads the same on paper and on charcoal.",
|
||||
),
|
||||
(
|
||||
"example.org",
|
||||
"Gamma and the eye",
|
||||
"https://example.org/notes/gamma",
|
||||
"Why even steps in gamma space look even to the eye.",
|
||||
),
|
||||
(
|
||||
"example.net",
|
||||
"Designing dark themes",
|
||||
"https://example.net/dark-themes",
|
||||
"Lift surfaces rather than shade them; depth reads as light.",
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(site, title, url, snippet)| Source {
|
||||
site: site.into(),
|
||||
title: title.into(),
|
||||
url: url.into(),
|
||||
snippet: Some(snippet.into()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn citations(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let all = sources();
|
||||
let cite = |ix: usize| {
|
||||
CitationBadge::new(("chat-cite", ix), ix + 1, all[ix].clone())
|
||||
.on_open(move |_, _| log::info!("gallery: open source {ix}"))
|
||||
};
|
||||
section(
|
||||
"CitationBadge / InlineCitation / SourceCard / SourceList / SourcesPanel",
|
||||
"Where an answer's words came from: a numbered pill after the claim, its source a rest of the pointer away; the sources folded under the answer, each a card.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(560.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_4()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child("A lift blends a color toward white, in gamma space.")
|
||||
.child(probe("chat-cite-1", cite(1)))
|
||||
.child("Dark themes lift further.")
|
||||
.child(cite(0))
|
||||
.child(cite(2)),
|
||||
)
|
||||
.child(probe(
|
||||
"chat-sources",
|
||||
div().w(px(560.)).child(
|
||||
SourceList::new("chat-sources", all.clone())
|
||||
.on_open(|ix, _, _| log::info!("gallery: open source {ix}")),
|
||||
),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
const PASSAGE: &str = "Accents in dark carry more lift than in light, because a saturated color on charcoal looks louder than the same color on paper.";
|
||||
|
||||
pub fn search(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let all = sources();
|
||||
let louder = PASSAGE
|
||||
.find("looks louder")
|
||||
.expect("the passage holds its match");
|
||||
let more = PASSAGE
|
||||
.find("more lift")
|
||||
.expect("the passage holds its match");
|
||||
section(
|
||||
"SearchProgress / WebResultCard / DocumentChunkPreview",
|
||||
"A search as it runs, step by step; a result as the web gives it; and a passage found in a document, the words that matched washed.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(560.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_4()
|
||||
.child(SearchProgress::new(
|
||||
"chat-search",
|
||||
[
|
||||
("Searching for “lift in dark themes”", StepState::Done),
|
||||
("Reading 3 sources", StepState::Working),
|
||||
("Writing the answer", StepState::Waiting),
|
||||
],
|
||||
))
|
||||
.child(
|
||||
WebResultCard::new("chat-result", all[2].clone())
|
||||
.date("Sep 2026")
|
||||
.on_open(|_, _| log::info!("gallery: open result")),
|
||||
)
|
||||
.child(
|
||||
DocumentChunkPreview::new(
|
||||
"lift-notes.pdf",
|
||||
"Page 1 · Dark is not inverted",
|
||||
PASSAGE,
|
||||
vec![more..more + "more lift".len(), louder..louder + "looks louder".len()],
|
||||
)
|
||||
.score(0.86),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod blocks;
|
||||
mod cite;
|
||||
mod controls;
|
||||
mod talk;
|
||||
|
||||
@@ -29,6 +30,15 @@ const SCRIPT: &[Step] = &[
|
||||
Step::Shot("editing"),
|
||||
Step::Key("escape"),
|
||||
Step::Wait(200),
|
||||
Step::HoverAt("chat-cite-1", 8.0, 8.0),
|
||||
Step::Wait(700),
|
||||
Step::Shot("citation"),
|
||||
Step::DownAt("chat-sources", 20.0, 10.0),
|
||||
Step::UpAt("chat-sources", 20.0, 10.0),
|
||||
Step::Wait(300),
|
||||
Step::Shot("sources"),
|
||||
Step::DownAt("chat-sources", 20.0, 10.0),
|
||||
Step::UpAt("chat-sources", 20.0, 10.0),
|
||||
];
|
||||
|
||||
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
@@ -41,5 +51,7 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
.child(controls::actions(window, cx))
|
||||
.child(controls::status(window, cx))
|
||||
.child(controls::thinking(window, cx))
|
||||
.child(cite::citations(window, cx))
|
||||
.child(cite::search(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
Reference in new issue
Block a user