Skip to content

Commit

Permalink
Add "regenerate" action and improve positioning
Browse files Browse the repository at this point in the history
  • Loading branch information
hecrj committed Jan 30, 2025
1 parent 924b081 commit c43d27f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 65 deletions.
1 change: 1 addition & 0 deletions fonts/icebreaker-icons.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ trash = "fontawesome-trash"
user = "iconic-user"
arrow_up = "entypo-up-open"
arrow_down = "entypo-down-open"
refresh = "fontawesome-arrows-cw"
Binary file modified fonts/icebreaker-icons.ttf
Binary file not shown.
50 changes: 34 additions & 16 deletions src/data/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ impl Chat {
let bytes = fs::read(Self::path(&id).await?).await?;
let schema: Schema = task::spawn_blocking(move || serde_json::from_slice(&bytes)).await??;

let _ = LastOpened::update(id).await;

Ok(Self {
id,
file: schema.file,
Expand All @@ -59,10 +61,10 @@ impl Chat {
let id = Id(Uuid::new_v4());
let chat = Self::save(id, file, title, history).await?;

LastOpened::update(chat.id.clone()).await?;
LastOpened::update(chat.id).await?;

List::push(Entry {
id: chat.id.clone(),
id: chat.id,
file: chat.file.clone(),
title: chat.title.clone(),
})
Expand All @@ -77,7 +79,7 @@ impl Chat {
title: Option<String>,
history: Vec<Message>,
) -> Result<Self, Error> {
if let Ok(current) = Self::fetch(id.clone()).await {
if let Ok(current) = Self::fetch(id).await {
if current.title != title {
let mut list = List::fetch().await?;

Expand Down Expand Up @@ -120,7 +122,7 @@ impl Chat {

match list.as_ref().and_then(|list| list.entries.first()) {
Some(entry) => {
LastOpened::update(entry.id.clone()).await?;
LastOpened::update(entry.id).await?;
}
None => {
LastOpened::delete().await?;
Expand Down Expand Up @@ -162,24 +164,15 @@ impl Content {
}
}

pub fn send(
pub fn complete(
assistant: &Assistant,
history: Vec<Message>,
message: Content,
mut messages: Vec<Message>,
) -> impl Stream<Item = Result<Event, Error>> {
const SYSTEM_PROMPT: &str = "You are a helpful assistant.";

let assistant = assistant.clone();
let mut messages = history.to_vec();
let message = message.as_str().to_owned();

iced::stream::try_channel(1, |mut sender| async move {
messages.push(Message::User(message.clone()));

let _ = sender
.send(Event::MessageSent(Message::User(message)))
.await;

let mut reasoning = String::new();
let mut reasoning_started_at = None;
let mut content = String::new();
Expand Down Expand Up @@ -276,7 +269,32 @@ pub fn send(
})
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub fn send(
assistant: &Assistant,
mut history: Vec<Message>,
message: Content,
) -> impl Stream<Item = Result<Event, Error>> {
let assistant = assistant.clone();
let message = message.as_str().to_owned();

iced::stream::try_channel(1, |mut sender| async move {
history.push(Message::User(message.clone()));

let _ = sender
.send(Event::MessageSent(Message::User(message)))
.await;

let mut task = complete(&assistant, history).boxed();

while let Some(result) = task.next().await {
let _ = sender.send(result?).await;
}

Ok(())
})
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Id(Uuid);

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
6 changes: 5 additions & 1 deletion src/icon.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated automatically by iced_fontello at build time.
// Do not edit manually.
// c503e19c014d4088df18af7446d35fed138cb328d6de87dbea1b9266a571c40c
// d0532da8de7e85e9222f7c911baa104de985f1b7f84f01e82bf71feb9968acce
use iced::widget::{text, Text};
use iced::Font;

Expand Down Expand Up @@ -42,6 +42,10 @@ pub fn heart<'a>() -> Text<'a> {
icon("\u{2665}")
}

pub fn refresh<'a>() -> Text<'a> {
icon("\u{E760}")
}

pub fn trash<'a>() -> Text<'a> {
icon("\u{F1F8}")
}
Expand Down
126 changes: 78 additions & 48 deletions src/screen/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ use iced::padding;
use iced::task::{self, Task};
use iced::time::{self, Duration, Instant};
use iced::widget::{
self, bottom_center, button, center, center_x, column, container, horizontal_space, hover,
markdown, progress_bar, right, right_center, row, scrollable, stack, text, text_editor,
tooltip, value, vertical_rule, vertical_space,
self, bottom, bottom_center, button, center, center_x, center_y, column, container,
horizontal_space, hover, markdown, progress_bar, right, right_center, row, scrollable, stack,
text, text_editor, tooltip, value, vertical_rule, vertical_space, Text,
};
use iced::{Center, Element, Fill, Font, Left, Rectangle, Right, Shrink, Subscription, Theme};
use iced::{Center, Element, Fill, Font, Rectangle, Shrink, Subscription, Theme};

pub struct Conversation {
backend: Backend,
Expand Down Expand Up @@ -54,6 +54,7 @@ pub enum Message {
Submit,
Chatting(Result<chat::Event, Error>),
Copy(Item),
Regenerate(usize),
ToggleReasoning(usize),
Created(Result<Chat, Error>),
Saved(Result<Chat, Error>),
Expand Down Expand Up @@ -283,6 +284,23 @@ impl Conversation {
Action::None
}
Message::Copy(message) => Action::Run(clipboard::write(message.into_text())),
Message::Regenerate(index) => {
if let State::Running { assistant, sending } = &mut self.state {
self.history.truncate(index);

let (send, handle) = Task::run(
chat::complete(assistant, self.history.messages().collect()),
Message::Chatting,
)
.abortable();

*sending = Some(handle.abort_on_drop());

Action::Run(send)
} else {
Action::None
}
}
Message::ToggleReasoning(index) => {
if let Some(Item::Assistant { show_reasoning, .. }) = self.history.get_mut(index) {
*show_reasoning = !*show_reasoning;
Expand Down Expand Up @@ -536,7 +554,6 @@ impl Conversation {
.enumerate()
.map(|(i, item)| item.view(i, theme)),
)
.spacing(40)
.padding(20)
.max_width(600),
)
Expand Down Expand Up @@ -732,6 +749,10 @@ impl History {
}
}

pub fn truncate(&mut self, amount: usize) {
self.items.truncate(amount);
}

pub fn messages<'a>(&'a self) -> impl Iterator<Item = assistant::Message> + 'a {

Check warning on line 756 in src/screen/conversation.rs

View workflow job for this annotation

GitHub Actions / all

the following explicit lifetimes could be elided: 'a

Check warning on line 756 in src/screen/conversation.rs

View workflow job for this annotation

GitHub Actions / all

the following explicit lifetimes could be elided: 'a
self.items.iter().cloned().map(assistant::Message::from)
}
Expand All @@ -755,7 +776,9 @@ impl Item {
pub fn view<'a>(&'a self, index: usize, theme: &Theme) -> Element<'a, Message> {
use iced::border;

let message: Element<_> = match self {
let copy = action(icon::clipboard(), "Copy", || Message::Copy(self.clone()));

match self {
Self::Assistant {
reasoning,
content,
Expand All @@ -770,7 +793,7 @@ impl Item {
)
.map(Message::UrlClicked);

if let Some(reasoning) = reasoning {
let message: Element<_> = if let Some(reasoning) = reasoning {
let toggle = button(
row![
text!(
Expand Down Expand Up @@ -814,53 +837,45 @@ impl Item {
column![reasoning, message].spacing(20).into()
} else {
message.into()

Check warning on line 839 in src/screen/conversation.rs

View workflow job for this annotation

GitHub Actions / all

useless conversion to the same type: `iced_core::element::Element<'_, screen::conversation::Message, iced::Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>>`

Check warning on line 839 in src/screen/conversation.rs

View workflow job for this annotation

GitHub Actions / all

useless conversion to the same type: `iced_core::element::Element<'_, screen::conversation::Message, iced::Theme, iced_renderer::fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>>`
}
};

let regenerate = action(icon::refresh(), "Regenerate", move || {
Message::Regenerate(index)
});

let actions = row![copy, regenerate].spacing(10);

hover(container(message).padding([30, 0]), bottom(actions))
}
Self::User {
markdown: content, ..
} => right(
container(
markdown(
content,
markdown::Settings::default(),
markdown::Style::from_palette(theme.palette()),
} => {
let message = container(
container(
markdown(
content,
markdown::Settings::default(),
markdown::Style::from_palette(theme.palette()),
)
.map(Message::UrlClicked),
)
.map(Message::UrlClicked),
.style(|theme: &Theme| {
let palette = theme.extended_palette();

container::Style {
background: Some(palette.background.weak.color.into()),
text_color: Some(palette.background.weak.text),
border: border::rounded(10),
..container::Style::default()
}
})
.padding(10),
)
.style(|theme: &Theme| {
let palette = theme.extended_palette();

container::Style {
background: Some(palette.background.weak.color.into()),
text_color: Some(palette.background.weak.text),
border: border::rounded(10),
..container::Style::default()
}
})
.padding(10),
)
.into(),
};
.padding(padding::all(20).left(30).right(0));

let copy = tip(
button(icon::clipboard())
.on_press_with(|| Message::Copy(self.clone()))
.padding(0)
.style(button::text),
"Copy to clipboard",
tip::Position::Bottom,
);

hover(
message,
container(copy)
.width(Fill)
.center_y(Fill)
.align_x(match self {
Self::Assistant { .. } => Right,
Self::User { .. } => Left,
}),
)
right(hover(message, center_y(copy))).into()
}
}
}

pub fn into_text(self) -> String {
Expand Down Expand Up @@ -891,3 +906,18 @@ fn measure_input() -> Task<Message> {
fn snap_chat_to_end() -> Task<Message> {
scrollable::snap_to(CHAT, scrollable::RelativeOffset::END)
}

fn action<'a>(
icon: Text<'a>,
label: &'a str,
message: impl Fn() -> Message + 'a,
) -> Element<'a, Message> {
tip(
button(icon)
.on_press_with(message)
.padding([2, 7])
.style(button::text),
label,
tip::Position::Bottom,
)
}

0 comments on commit c43d27f

Please sign in to comment.