Skip to content

Commit

Permalink
Rewrite all UIs
Browse files Browse the repository at this point in the history
  • Loading branch information
AurevoirXavier committed Jul 14, 2024
1 parent 26d9023 commit 80c0be5
Show file tree
Hide file tree
Showing 13 changed files with 409 additions and 330 deletions.
1 change: 0 additions & 1 deletion asset/retry-dark.svg

This file was deleted.

1 change: 0 additions & 1 deletion asset/retry-light.svg

This file was deleted.

1 change: 1 addition & 0 deletions asset/send-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions asset/send-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions dev/text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be counting stars. Yeah, we'll be counting stars. I see this life, like a swinging vine. Swing my heart across the line. And in my face is flashing signs. Seek it out and ye' shall find. Old, but I'm not that old. Young, but I'm not that bold. And I don't think the world is sold. On just doing what we're told. I feel something so right doing the wrong thing. I feel something so wrong doing the right thing. I couldn't lie, couldn't lie, couldn't lie. Everything that kills me makes me feel alive. Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be counting stars. Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be, we'll be counting stars. I feel your love and I feel it burn. Down this river, every turn. Hope is our four-letter word. Make that money, watch it burn. Old, but I'm not that old. Young, but I'm not that bold. And I don't think the world is sold. On just doing what we're told. I feel something so wrong doing the right thing. I couldn't lie, couldn't lie, couldn't lie. Everything that drowns me makes me wanna fly. Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be counting stars. Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be, we'll be counting stars. Take that money, watch it burn. Sink in the river the lessons I learned. Take that money, watch it burn. Sink in the river the lessons I learned. Take that money, watch it burn. Sink in the river the lessons I learned. Take that money, watch it burn. Sink in the river the lessons I learned. Everything that kills me makes me feel alive. Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be counting stars. Lately, I've been, I've been losing sleep. Dreaming about the things that we could be. But baby, I've been, I've been praying hard. Said no more counting dollars, we'll be, we'll be counting stars. Take that money, watch it burn. Sink in the river the lessons I've learned. Take that money, watch it burn. Sink in the river the lessons I've learned. Take that money, watch it burn. Sink in the river the lessons I've learned. Take that money, watch it burn. Sink in the river the lessons I've learned.
4 changes: 2 additions & 2 deletions src/air.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ impl App for AiR {
self.once.call_once(Os::set_move_to_active_space);
}
// TODO: https://github.com/emilk/egui/issues/4468.
// Allow 250ms for initialization during the first boot.
else if raw_input.time.unwrap_or_default() >= 0.25
// Allow 1,000ms for initialization during the first boot.
else if raw_input.time.unwrap_or_default() >= 1.
&& self.components.setting.general.hide_on_lost_focus
{
// TODO: https://github.com/emilk/egui/discussions/4635.
Expand Down
43 changes: 15 additions & 28 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,36 @@
mod panel;
use panel::{Chat, Panel, Setting};
use panel::*;

// crates.io
use eframe::egui::*;
// self
use crate::air::AiRContext;

trait UiT {
fn draw(&mut self, ui: &mut Ui, ctx: &mut AiRContext);
}

#[derive(Debug, Default)]
pub struct Uis {
focused_panel: Panel,
tabs: Tabs,
chat: Chat,
setting: Setting,
status: Status,
}
impl Uis {
pub fn new() -> Self {
Default::default()
}

pub fn draw(&mut self, mut ctx: AiRContext) {
CentralPanel::default()
// TODO:? transparent window.
// FIXME: it looks like there some invalid cache.
// .frame(util::transparent_frame(ctx.egui_ctx))
.show(ctx.egui_ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut self.focused_panel, Panel::Chat, Panel::Chat.name());
ui.separator();
ui.selectable_value(
&mut self.focused_panel,
Panel::Setting,
Panel::Setting.name(),
);
ui.separator();
});
ui.separator();

match self.focused_panel {
Panel::Chat => self.chat.draw(ui, &mut ctx),
Panel::Setting => self.setting.draw(ui, &mut ctx),
}
});
let bar_h = ctx.components.setting.general.font_size + 10.;

// Tabs.
TopBottomPanel::top("Top Panel").show(ctx.egui_ctx, |ui| self.tabs.draw(ui, bar_h));
// Main body.
CentralPanel::default().show(ctx.egui_ctx, |ui| match self.tabs.focused_tab {
Panel::Chat => self.chat.draw(&mut ctx, ui, bar_h),
Panel::Setting => self.setting.draw(&mut ctx, ui, bar_h),
});
// Status bar.
TopBottomPanel::bottom("Bottom Panel")
.show(ctx.egui_ctx, |ui| self.status.draw(&mut ctx, ui, bar_h, &self.chat));
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/ui/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ pub use chat::Chat;
mod setting;
pub use setting::Setting;

mod status;
pub use status::Status;

mod tabs;
pub use tabs::Tabs;

#[derive(Debug, PartialEq, Eq)]
pub enum Panel {
Chat,
Expand Down
220 changes: 71 additions & 149 deletions src/ui/panel/chat.rs
Original file line number Diff line number Diff line change
@@ -1,187 +1,109 @@
// std
use std::sync::atomic::Ordering;
// crates.io
use eframe::egui::*;
// self
use super::super::UiT;
use crate::{air::AiRContext, component::openai::Model, util, widget};
use crate::{air::AiRContext, widget::SMALL_FONT_OFFSET};

#[derive(Debug, Default)]
#[cfg_attr(not(feature = "dev"), derive(Default))]
#[derive(Debug)]
pub struct Chat {
input: String,
output: String,
shortcut: ShortcutWidget,
pub input: String,
pub output: String,
}
impl UiT for Chat {
fn draw(&mut self, ui: &mut Ui, ctx: &mut AiRContext) {
let dark_mode = ui.visuals().dark_mode;
let size = ui.available_size();
let is_chatting = ctx.services.is_chatting();

ScrollArea::vertical().id_source("Input").max_height((size.y - 50.) / 2.).show(ui, |ui| {
let input = ui.add_sized(
(size.x, ui.available_height()),
TextEdit::multiline({
if is_chatting {
if let Some(i) = ctx.state.chat.input.try_read() {
i.clone_into(&mut self.input);
}
}
impl Chat {
pub fn draw(&mut self, ctx: &mut AiRContext, ui: &mut Ui, bar_height: f32) {
let size = ui.min_rect().size();
let h = size.y - bar_height * 2.;
let separator_h = ui.spacing().item_spacing.y * 2.;
// TODO: this isn't really the height.
let shortcut_h = ctx.components.setting.general.font_size;
let scroll_h = (h - separator_h - shortcut_h) / 2.;
// let scroll_h = (h - shortcut_h) / 2.;

&mut self.input
})
.hint_text(&*ctx.state.chat.quote.read()),
);
// dbg!(size.y, h, shortcut_h, scroll_h);

if input.has_focus() {
self.shortcut.copy.triggered = false;
let dark_mode = ui.visuals().dark_mode;
let is_chatting = ctx.services.is_chatting();

let to_send = input.ctx.input(|i| {
let modifier = if cfg!(target_os = "macos") {
i.modifiers.mac_cmd
} else {
i.modifiers.ctrl
};
// Input.
ui.vertical(|ui| {
ui.set_height(scroll_h);

modifier && i.key_pressed(Key::Enter)
});
ScrollArea::vertical().id_source("Input").show(ui, |ui| {
let input = ui.add_sized(
(size.x, scroll_h),
TextEdit::multiline({
if is_chatting {
if let Some(i) = ctx.state.chat.input.try_read() {
i.clone_into(&mut self.input);
}
}

if to_send {
ctx.services.chat.send((
ctx.components.setting.general.active_func.basic(),
self.input.clone(),
false,
));
}
}
});
&mut self.input
})
.hint_text(&*ctx.state.chat.quote.read()),
);

// Indicators.
ui.horizontal(|ui| {
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
ui.vertical(|ui| {
ui.add_space(4.5);
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
let tcs = &ctx.state.chat.token_counts;
let (itc, otc) =
(tcs.0.load(Ordering::Relaxed), tcs.1.load(Ordering::Relaxed));
let (ip, op) = ctx.components.setting.ai.model.prices();
if input.has_focus() {
let to_send = input.ctx.input(|i| {
let modifier = if cfg!(target_os = "macos") {
i.modifiers.mac_cmd
} else {
i.modifiers.ctrl
};

ui.hyperlink_to(format!(
"input: {itc} output: {otc} price: ${:.6}",
util::price_rounded(itc as f32 * ip + otc as f32 * op)
),Model::PRICE_URI).on_hover_text("The token indicator might not work if you are using a custom API provider.");
modifier && i.key_pressed(Key::Enter)
});
});
});
});

ui.separator();

// Shortcuts.
ui.horizontal(|ui| {
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if is_chatting {
ui.spinner();
} else {
// TODO: change retry to send.
// TODO: the state will not be synced if previous action is triggered by hotkey.
if ui.add(self.shortcut.retry.img(dark_mode)).clicked() {
if to_send {
ctx.services.chat.send((
ctx.components.setting.general.active_func.basic(),
self.input.clone(),
false,
));
}
}
if !self.shortcut.copy.triggered {
if ui.add(self.shortcut.copy.copy_img(dark_mode)).clicked() {
self.shortcut.copy.triggered = true;
ctx.components
.clipboard
.set_text(&self.output)
.expect("clipboard must be available");
}
} else {
ui.add(self.shortcut.copy.copied_img(dark_mode));
}
});
});
ui.separator();
// Information.
ui.horizontal(|ui| {
let tip = RichText::new(if is_chatting {
"Thinking"
} else {
"Press CTRL/CMD+Enter to Send"
})
.color(if dark_mode { Color32::GOLD } else { Color32::BROWN });

ScrollArea::vertical().id_source("Output").show(ui, |ui| {
ui.label({
ui.set_height(shortcut_h);
ui.with_layout(Layout::centered_and_justified(Direction::LeftToRight), |ui| {
ui.label(tip.size(ctx.components.setting.general.font_size - SMALL_FONT_OFFSET));
});
});
ui.separator();
// Output.
ui.vertical(|ui| {
ui.set_height(scroll_h);

ScrollArea::vertical().id_source("Output").show(ui, |ui| {
if is_chatting {
if let Some(o) = ctx.state.chat.output.try_read() {
o.clone_into(&mut self.output);
}
}

&self.output
// Read-only trick.
let mut output = self.output.as_str();

ui.add_sized((size.x, scroll_h), TextEdit::multiline(&mut output));
});
});
}
}

#[derive(Debug, Default)]
struct ShortcutWidget {
copy: CopyWidget,
retry: RetryWidget,
}
#[derive(Debug)]
struct CopyWidget {
copy_img_l: Image<'static>,
copy_img_d: Image<'static>,
copied_img_l: Image<'static>,
copied_img_d: Image<'static>,
triggered: bool,
}
impl CopyWidget {
fn copy_img(&self, dark_mode: bool) -> Image<'static> {
if dark_mode {
self.copy_img_d.clone()
} else {
self.copy_img_l.clone()
}
}

fn copied_img(&self, dark_mode: bool) -> Image<'static> {
if dark_mode {
self.copied_img_d.clone()
} else {
self.copied_img_l.clone()
}
}
}
impl Default for CopyWidget {
fn default() -> Self {
Self {
copy_img_d: widget::image_button(include_image!("../../../asset/copy-dark.svg")),
copy_img_l: widget::image_button(include_image!("../../../asset/copy-light.svg")),
copied_img_d: widget::image_button(include_image!("../../../asset/copied-dark.svg")),
copied_img_l: widget::image_button(include_image!("../../../asset/copied-light.svg")),
triggered: false,
}
}
}
#[derive(Debug)]
struct RetryWidget {
retry_img_d: Image<'static>,
retry_img_l: Image<'static>,
}
impl RetryWidget {
fn img(&self, dark_mode: bool) -> Image<'static> {
if dark_mode {
self.retry_img_d.clone()
} else {
self.retry_img_l.clone()
}
}
}
impl Default for RetryWidget {
#[cfg(feature = "dev")]
impl Default for Chat {
fn default() -> Self {
Self {
retry_img_d: widget::image_button(include_image!("../../../asset/retry-dark.svg")),
retry_img_l: widget::image_button(include_image!("../../../asset/retry-light.svg")),
}
const TEXT: &str = include_str!("../../../dev/text.txt");

Self { input: TEXT.into(), output: TEXT.into() }
}
}
Loading

0 comments on commit 80c0be5

Please sign in to comment.