gpui and gpui_platform now come from Zed's repo by git at 1a28cff, the code gpui-pre 0.3.7 republishes, so the gallery can later build for the browser. gpui takes stacker, as 0.2.2 always did, and gpui_platform takes font-kit, without which macOS draws no text; runtime_shaders moves to gpui_platform. The API moved: focus, blur and Tab steps take cx, Corner is Anchor, shaped lines paint with an alignment, defer_draw takes a content mask, the inspector registers a factory, async updates return their value, and wrapped lines are shared, not cloned. Updates through a window context go through the window, so a closed window still ends them as it did. Where the new gpui behaves differently, Ely keeps its old intent. Holds and reveals under a resting pointer survive typing through gpui's input-modality-independent hover; previews the keyboard also moves keep the default, and the calendar's leave ignores a key press. Enter reaches a button inside the code editor, since gpui now arms a key click on the press. The event logger skips a lone modifier. A video and an image grid keep their shape in a flex column under taffy 0.13, as a picture does. AGENTS.md records the new stack and the rules this port changes; the rest of the old gpui and taffy rules are re-tested in T44c.
312 lines
10 KiB
Rust
312 lines
10 KiB
Rust
use std::time::Duration;
|
|
|
|
use ely_gpui_component::{
|
|
data_display::{DescriptionList, Tone},
|
|
forms::TextInput,
|
|
generative::{
|
|
Embedded, EmbeddingVisualizer, PromptPlayground, PromptVersion, PromptVersionHistory,
|
|
},
|
|
tables::{Cell, Column, DataTable, Row},
|
|
};
|
|
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px, rems};
|
|
use jiff::{SignedDuration, Timestamp};
|
|
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{keep, section, set},
|
|
};
|
|
|
|
const SAMPLES: [(&str, &str, bool, f64); 12] = [
|
|
(
|
|
"Summarize the meeting notes in two lines.",
|
|
"The launch moves to May. Design owns the new onboarding flow.",
|
|
true,
|
|
38.0,
|
|
),
|
|
(
|
|
"Turn this list into a friendly reminder.",
|
|
"A quick note: the lab closes at six on Friday. Please take your samples home.",
|
|
true,
|
|
41.0,
|
|
),
|
|
(
|
|
"Translate 'good morning' into Spanish.",
|
|
"Buenos días.",
|
|
true,
|
|
12.0,
|
|
),
|
|
(
|
|
"Write a subject line for a refund email.",
|
|
"Your refund is on its way",
|
|
false,
|
|
17.0,
|
|
),
|
|
(
|
|
"Explain a mutex to a new engineer.",
|
|
"A mutex lets one thread touch shared data at a time. The others wait their turn.",
|
|
true,
|
|
36.0,
|
|
),
|
|
("Suggest a name for a hiking app.", "Trailhead", true, 11.0),
|
|
(
|
|
"Rewrite without jargon: 'We will leverage synergies.'",
|
|
"We will work together.",
|
|
false,
|
|
24.0,
|
|
),
|
|
(
|
|
"List three checks before a deploy.",
|
|
"Tests pass. Migrations run. Someone is on call.",
|
|
true,
|
|
29.0,
|
|
),
|
|
("Answer in one word: is 91 prime?", "No.", false, 13.0),
|
|
(
|
|
"Draft a two-line product update.",
|
|
"Search is twice as fast. Filters now remember your last choice.",
|
|
true,
|
|
33.0,
|
|
),
|
|
(
|
|
"Give a calm reply to an angry customer.",
|
|
"I'm sorry for the trouble. I've refunded the order and will check in tomorrow.",
|
|
true,
|
|
40.0,
|
|
),
|
|
("Name the capital of Canada.", "Ottawa.", false, 10.0),
|
|
];
|
|
|
|
pub fn dataset(cx: &mut App) -> impl IntoElement + use<> {
|
|
let rows: Vec<Row> = SAMPLES
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(ix, (prompt, answer, train, tokens))| {
|
|
let split = if *train {
|
|
("Train", Tone::Neutral)
|
|
} else {
|
|
("Held out", Tone::Info)
|
|
};
|
|
Row::new(
|
|
SharedString::from(format!("sample-{ix}")),
|
|
[
|
|
Cell::Text((*prompt).into()),
|
|
Cell::Text((*answer).into()),
|
|
Cell::Tag(split.0.into(), split.1),
|
|
Cell::Number(*tokens),
|
|
],
|
|
)
|
|
})
|
|
.collect();
|
|
section(
|
|
"DatasetViewer → tables::DataTable",
|
|
"Samples of a dataset: a prompt, its answer, its split and its length. Sort by any column, page through, and open a sample to read it whole.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"gen-dataset",
|
|
div().w(px(680.)).child(
|
|
DataTable::new(
|
|
"gen-dataset",
|
|
[
|
|
Column::new("prompt", "Prompt"),
|
|
Column::new("answer", "Answer"),
|
|
Column::new("split", "Split").width(rems(6.)),
|
|
Column::new("tokens", "Tokens").width(rems(5.)).end().decimals(0),
|
|
],
|
|
)
|
|
.rows(rows)
|
|
.paged(5)
|
|
.detail(|key, _, _| {
|
|
let ix: usize = key.trim_start_matches("sample-").parse().expect("a sample's key");
|
|
let (prompt, answer, _, _) = SAMPLES[ix];
|
|
DescriptionList::new().stacked().item("Prompt", prompt).item("Answer", answer).into_any_element()
|
|
}),
|
|
),
|
|
))
|
|
}
|
|
|
|
/// A number from 0 to 1 that looks random and stays the same for its seed.
|
|
fn noise(seed: u32) -> f32 {
|
|
let mut x = (seed + 1).wrapping_mul(0x9E37_79B9);
|
|
x ^= x >> 16;
|
|
x = x.wrapping_mul(0x7FEB_352D);
|
|
x ^= x >> 15;
|
|
x = x.wrapping_mul(0x846C_A68B);
|
|
x ^= x >> 16;
|
|
x as f32 / u32::MAX as f32
|
|
}
|
|
|
|
/// Notes in four topics, gathered near each topic's middle.
|
|
fn notes() -> Vec<Embedded> {
|
|
let middles = [
|
|
[-0.5, 0.4, 0.2],
|
|
[0.45, 0.35, -0.3],
|
|
[0.1, -0.5, 0.4],
|
|
[-0.3, -0.2, -0.5],
|
|
];
|
|
(0..64)
|
|
.map(|ix| {
|
|
let group = ix % 4;
|
|
let seed = (ix * 6) as u32;
|
|
let jitter =
|
|
|axis: u32| (noise(seed + axis * 2) + noise(seed + axis * 2 + 1) - 1.0) * 0.3;
|
|
Embedded {
|
|
key: format!("note-{ix}").into(),
|
|
label: format!("Note {}", ix + 1).into(),
|
|
group,
|
|
at: [0, 1, 2]
|
|
.map(|axis| (middles[group][axis as usize] + jitter(axis)).clamp(-1.0, 1.0)),
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn embeddings(cx: &mut App) -> impl IntoElement + use<> {
|
|
let topics = ["Recipes", "Travel", "Finance", "Code"];
|
|
section(
|
|
"EmbeddingVisualizer",
|
|
"Notes placed by what they mean, colored by topic, flat or turned in three dimensions. Drag the turned one, or use the arrow keys. The pointer names a note; the legend hides a topic.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"gen-embed",
|
|
div()
|
|
.flex()
|
|
.flex_wrap()
|
|
.gap_6()
|
|
.child(div().w(px(340.)).child(EmbeddingVisualizer::new("gen-embed-flat", notes(), topics)))
|
|
.child(div().w(px(340.)).child(EmbeddingVisualizer::new("gen-embed-turned", notes(), topics).three_dimensions(true))),
|
|
))
|
|
}
|
|
|
|
/// What the playground shows: running, and the last answer with its tokens and time.
|
|
#[derive(Clone, Default)]
|
|
struct Play {
|
|
running: bool,
|
|
answer: Option<(SharedString, usize, Duration)>,
|
|
}
|
|
|
|
pub fn playground(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let template = window.use_keyed_state("gen-play-template", cx, |window, cx| {
|
|
let mut input = TextInput::new(window, cx).multi_line(2, 5);
|
|
input.set_text("Write three short notes on {topic} in a {tone} voice.", cx);
|
|
input
|
|
});
|
|
let play = keep("gen-play", Play::default, window, cx);
|
|
let now = play.read(cx).clone();
|
|
let playground = PromptPlayground::new("gen-play", &template)
|
|
.running(now.running)
|
|
.on_run(move |_, _, cx| {
|
|
set(
|
|
&play,
|
|
Play {
|
|
running: true,
|
|
answer: None,
|
|
},
|
|
cx,
|
|
);
|
|
let play = play.clone();
|
|
cx.spawn(async move |cx| {
|
|
cx.background_executor()
|
|
.timer(Duration::from_millis(900))
|
|
.await;
|
|
cx.update(|cx| {
|
|
let answer =
|
|
"Start with what you saw. Keep each line short. End on one plain fact.";
|
|
set(
|
|
&play,
|
|
Play {
|
|
running: false,
|
|
answer: Some((answer.into(), 18, Duration::from_millis(1240))),
|
|
},
|
|
cx,
|
|
)
|
|
});
|
|
})
|
|
.detach();
|
|
});
|
|
let playground = match now.answer {
|
|
Some((text, tokens, took)) => playground.output(text, tokens, took),
|
|
None => playground,
|
|
};
|
|
section(
|
|
"PromptPlayground",
|
|
"A prompt to try. Each {name} in the template gets a field, and the prompt below marks what is still blank. Run waits for every value, then shows the answer with its tokens and time.",
|
|
cx,
|
|
)
|
|
.child(probe("gen-play", div().w(px(520.)).child(playground)))
|
|
}
|
|
|
|
fn history() -> Vec<PromptVersion> {
|
|
let now = Timestamp::now();
|
|
let version = |key: &str, text: &str, note: &str, hours: i64| PromptVersion {
|
|
key: key.to_string().into(),
|
|
text: text.to_string().into(),
|
|
author: if hours > 30 { "Mira" } else { "Theo" }.into(),
|
|
at: now - SignedDuration::from_hours(hours),
|
|
note: note.to_string().into(),
|
|
};
|
|
vec![
|
|
version(
|
|
"v4",
|
|
"You answer support questions. Write plain, short replies. Say sorry once. Offer the next step.",
|
|
"Offer the next step",
|
|
3,
|
|
),
|
|
version(
|
|
"v3",
|
|
"You answer support questions. Write plain, short replies. Say sorry once.",
|
|
"Sorry once, not every line",
|
|
26,
|
|
),
|
|
version(
|
|
"v2",
|
|
"You answer support questions. Write plain replies. Always say sorry.",
|
|
"Plain words",
|
|
50,
|
|
),
|
|
version(
|
|
"v1",
|
|
"You are a helpful assistant. Always say sorry.",
|
|
"First draft",
|
|
98,
|
|
),
|
|
]
|
|
}
|
|
|
|
pub fn versions(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let kept = keep("gen-versions", || (history(), 1usize), window, cx);
|
|
let (list, chosen) = kept.read(cx).clone();
|
|
let (choose, restore) = (kept.clone(), kept.clone());
|
|
section(
|
|
"PromptDiff / PromptVersionHistory",
|
|
"A prompt's versions, newest first, and what the chosen one changed: words taken out struck through, words put in washed green. Restore brings an older one back as the newest.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"gen-versions",
|
|
div().w(px(640.)).child(
|
|
PromptVersionHistory::new("gen-versions", list, chosen)
|
|
.on_choose(move |ix, _, cx| {
|
|
let list = choose.read(cx).0.clone();
|
|
set(&choose, (list, ix), cx)
|
|
})
|
|
.on_restore(move |ix, _, cx| {
|
|
let mut list = restore.read(cx).0.clone();
|
|
let back = list[ix].clone();
|
|
let place = list.len() - ix;
|
|
list.insert(
|
|
0,
|
|
PromptVersion {
|
|
key: format!("v{}", list.len() + 1).into(),
|
|
at: Timestamp::now(),
|
|
note: format!("Restored version {place}").into(),
|
|
..back
|
|
},
|
|
);
|
|
set(&restore, (list, 0), cx)
|
|
}),
|
|
),
|
|
))
|
|
}
|