T25b Generative: queue and results
GenerationQueue shows each job's picture or where it stands, its prompt and its progress, with Cancel, Retry and Remove as its state allows; new rows fold in. GenerationGrid lays a run's results out in their shape: breathing while they come, the picture once done, why one failed with Retry. VariationPicker shows the chosen variation large over thumbnails and ways on from it. ABCompareView sets two to four models' answers side by side, blind until a verdict. ImageCompare points to data_display::BeforeAfter. primitives::framed and checked_ratio now serve previews and generative alike. AGENTS.md records that gpui reads a &str picture as a web address, and that grid rows overgrow tiles that keep a ratio.
This commit is contained in:
1 parent
f0431411d5
commit
81d8d52a09
17 files changed
+1515
-215
No files matched your search
@@ -0,0 +1,192 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
Animation, AnimationExt, App, ElementId, InteractiveElement, IntoElement, MouseButton,
|
||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div,
|
||||
transparent_black,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
buttons::{Button, ButtonVariant},
|
||||
documents::source,
|
||||
forms::{OnFlag, Pick, Run},
|
||||
motion,
|
||||
primitives::{IconName, Image, checked_ratio, framed, tab_stop},
|
||||
theme::{ActiveTheme, AvatarSize, ControlSize, Radius},
|
||||
};
|
||||
|
||||
/// Variations of one picture: the chosen one large, every one as a thumbnail to choose, and ways on from the chosen: vary it a little or a lot, or upscale it. The large picture fades in on each choice.
|
||||
#[derive(IntoElement)]
|
||||
pub struct VariationPicker {
|
||||
id: ElementId,
|
||||
pictures: Vec<SharedString>,
|
||||
ratio: f32,
|
||||
chosen: usize,
|
||||
on_choose: Option<Pick>,
|
||||
on_vary: Option<OnFlag>,
|
||||
on_upscale: Option<Run>,
|
||||
}
|
||||
|
||||
impl VariationPicker {
|
||||
/// `ratio` is the pictures' width over height; `chosen` indexes `pictures`.
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
pictures: impl IntoIterator<Item = impl Into<SharedString>>,
|
||||
ratio: f32,
|
||||
chosen: usize,
|
||||
) -> Self {
|
||||
let pictures: Vec<SharedString> = pictures.into_iter().map(Into::into).collect();
|
||||
assert!(
|
||||
chosen < pictures.len(),
|
||||
"variation {chosen} of {}",
|
||||
pictures.len()
|
||||
);
|
||||
Self {
|
||||
id: id.into(),
|
||||
pictures,
|
||||
ratio: checked_ratio(ratio),
|
||||
chosen,
|
||||
on_choose: None,
|
||||
on_vary: None,
|
||||
on_upscale: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_choose(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_choose = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets whether to vary a lot.
|
||||
pub fn on_vary(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_vary = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_upscale(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_upscale = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for VariationPicker {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let turn = motion::changes((self.id.clone(), "turns"), self.chosen, window, cx);
|
||||
let thumbs: Vec<_> = (0..self.pictures.len())
|
||||
.map(|ix| {
|
||||
let focus = tab_stop(
|
||||
(self.id.clone(), format!("focus-{ix}")).into(),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let focused = focus.is_focused(window);
|
||||
(focus, focused)
|
||||
})
|
||||
.collect();
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
let (large, small) = (theme.radius(Radius::Lg), theme.radius(Radius::Md));
|
||||
let thumb = theme.avatar_size(AvatarSize::Xl);
|
||||
let length = motion::duration(motion::BASE, cx);
|
||||
let picture = Image::new(
|
||||
(self.id.clone(), format!("large-{}", self.chosen)),
|
||||
source(&self.pictures[self.chosen]),
|
||||
)
|
||||
.size_full()
|
||||
.rounded(large);
|
||||
let shown = match turn {
|
||||
0 => picture.into_any_element(),
|
||||
_ => div()
|
||||
.size_full()
|
||||
.child(picture)
|
||||
.with_animation(
|
||||
(self.id.clone(), format!("fade-{turn}")),
|
||||
Animation::new(length).with_easing(motion::ease_out_cubic),
|
||||
|shown, t| shown.opacity(0.4 + 0.6 * t),
|
||||
)
|
||||
.into_any_element(),
|
||||
};
|
||||
let vary = |strong: bool, label: &'static str| {
|
||||
self.on_vary.clone().map(|vary| {
|
||||
Button::new((self.id.clone(), label), label)
|
||||
.variant(ButtonVariant::Secondary)
|
||||
.size(ControlSize::Sm)
|
||||
.icon(IconName::Shuffle)
|
||||
.on_click(move |_, window, cx| {
|
||||
log::info!("variation picker: vary, strong {strong}");
|
||||
vary(strong, window, cx)
|
||||
})
|
||||
})
|
||||
};
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(framed(self.ratio, cx).rounded(large).child(shown))
|
||||
.child(div().flex().flex_wrap().gap_2().children(
|
||||
self.pictures.iter().zip(thumbs).enumerate().map(
|
||||
|(ix, (picture, (focus, focused)))| {
|
||||
let chosen = ix == self.chosen;
|
||||
let choose = self.on_choose.clone();
|
||||
div()
|
||||
.id((self.id.clone(), format!("thumb-{ix}")))
|
||||
.track_focus(&focus)
|
||||
.w(thumb)
|
||||
.p_0p5()
|
||||
.rounded(large)
|
||||
.border_1()
|
||||
.border_color(if focused {
|
||||
colors.focus
|
||||
} else if chosen {
|
||||
colors.accent
|
||||
} else {
|
||||
transparent_black()
|
||||
})
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(MouseButton::Left, |_, window, _| {
|
||||
window.prevent_default()
|
||||
})
|
||||
.on_click(move |_, window, cx| {
|
||||
if chosen {
|
||||
return;
|
||||
}
|
||||
log::info!("variation picker: chose {ix}");
|
||||
if let Some(choose) = &choose {
|
||||
choose(ix, window, cx);
|
||||
}
|
||||
})
|
||||
.child(
|
||||
framed(self.ratio, cx).rounded(small).child(
|
||||
Image::new(
|
||||
(self.id.clone(), format!("thumb-picture-{ix}")),
|
||||
source(picture),
|
||||
)
|
||||
.size_full()
|
||||
.rounded(small),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.gap_2()
|
||||
.children(vary(false, "Vary subtly"))
|
||||
.children(vary(true, "Vary strongly"))
|
||||
.children(self.on_upscale.clone().map(|upscale| {
|
||||
Button::new((self.id.clone(), "upscale"), "Upscale")
|
||||
.variant(ButtonVariant::Secondary)
|
||||
.size(ControlSize::Sm)
|
||||
.icon(IconName::Maximize2)
|
||||
.on_click(move |_, window, cx| {
|
||||
log::info!("variation picker: upscale");
|
||||
upscale(window, cx)
|
||||
})
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user