From 2bd15a7d832bb49c9a5a43eb9475dde1cb4696b3 Mon Sep 17 00:00:00 2001 From: Vika Date: Thu, 22 Aug 2024 18:30:14 +0300 Subject: SmartSummaryButton: un-asyncify and move summarization to a command What this command should do is construct a summarization request and return a future which would return chunks from the LLM. Perhaps this component will be asyncified in the future. --- src/components/smart_summary.rs | 65 ++++++++++++++++++++++++++++------------- src/lib.rs | 30 ++++++++++++------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/components/smart_summary.rs b/src/components/smart_summary.rs index b360d77..fbfc80b 100644 --- a/src/components/smart_summary.rs +++ b/src/components/smart_summary.rs @@ -1,5 +1,5 @@ use adw::prelude::*; -use relm4::{gtk, prelude::{AsyncComponent, AsyncComponentParts}, AsyncComponentSender}; +use relm4::{gtk, prelude::{Component, ComponentParts}, ComponentSender}; #[derive(Debug, Default)] pub(crate) struct SmartSummaryButton { @@ -13,8 +13,9 @@ pub(crate) enum Error { #[derive(Debug)] pub(crate) enum Input { - ButtonPressed, - Text(String) + #[doc(hidden)] ButtonPressed, + Text(String), + Cancel, } #[derive(Debug)] @@ -26,13 +27,13 @@ pub(crate) enum Output { Error(Error) } -#[relm4::component(pub(crate) async)] -impl AsyncComponent for SmartSummaryButton { +#[relm4::component(pub(crate))] +impl Component for SmartSummaryButton { type Input = Input; type Output = Output; type Init = (); - type CommandOutput = (); + type CommandOutput = Result; view! { #[root] @@ -52,44 +53,66 @@ impl AsyncComponent for SmartSummaryButton { } } - async fn init( + fn init( _init: Self::Init, root: Self::Root, - sender: AsyncComponentSender - ) -> AsyncComponentParts { + sender: ComponentSender + ) -> ComponentParts { let model = Self::default(); let widgets = view_output!(); - AsyncComponentParts { model, widgets } + ComponentParts { model, widgets } } - async fn update_with_view( + fn update( &mut self, - widgets: &mut Self::Widgets, msg: Self::Input, - sender: AsyncComponentSender, + sender: ComponentSender, _root: &Self::Root ) { match msg { + Input::Cancel => { + self.busy = false; + log::debug!("Parent component asked us to cancel."); + }, Input::ButtonPressed => if let Ok(()) = sender.output(Output::Start) { self.busy = true; log::debug!("Requesting text to summarize from parent component..."); - self.update_view(widgets, sender.clone()); }, Input::Text(text) => { log::debug!("Would generate summary for the following text:\n{}", text); - tokio::time::sleep(std::time::Duration::from_millis(450)).await; - for i in ["I'", "m ", "sorry,", " I", " am ", "unable", " to", " ", "write", " you ", "a summary.", " I", " am", " not ", "really ", "an ", "LLM."] { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - let _ = sender.output(Output::Chunk(i.to_string())); - } + sender.command(|sender, shutdown| shutdown.register(async move { + tokio::time::sleep(std::time::Duration::from_millis(450)).await; + + for i in ["I'", "m ", "sorry,", " I", " am ", "unable", " to", " ", "write", " you ", "a summary.", " I", " am", " not ", "really ", "an ", "LLM."] { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + + if sender.send(Ok(i.to_string())).is_err() { + return + }; + } + + log::debug!("Done with the summary."); + let _ = sender.send(Ok(String::new())); + }).drop_on_shutdown()); + } + } + } - log::debug!("Done with the summary."); + fn update_cmd(&mut self, msg: Self::CommandOutput, sender: ComponentSender, _root: &Self::Root) { + match msg { + Ok(chunk) if chunk.is_empty() => { self.busy = false; let _ = sender.output(Output::Done); - self.update_view(widgets, sender.clone()); + }, + Err(err) => { + self.busy = false; + let _ = sender.output(Output::Error(err)); } + Ok(chunk) => { + let _ = sender.output(Output::Chunk(chunk)); + }, } } } diff --git a/src/lib.rs b/src/lib.rs index 244c09f..8e73a33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use adw::prelude::*; use gtk::GridLayoutChild; -use relm4::{gtk, prelude::{AsyncComponent, AsyncComponentController, AsyncComponentParts, AsyncController}, AsyncComponentSender, RelmWidgetExt}; +use relm4::{gtk, prelude::{AsyncComponent, AsyncComponentController, AsyncComponentParts, AsyncController, ComponentController, Controller}, AsyncComponentSender, Component, RelmWidgetExt}; pub mod components { pub(crate) mod smart_summary; @@ -35,7 +35,7 @@ pub struct PostComposerModel { #[do_not_track] micropub: Arc, - #[do_not_track] smart_summary: AsyncController, + #[do_not_track] smart_summary: Controller, } impl PostComposerModel { @@ -342,18 +342,28 @@ impl AsyncComponent for PostComposerModel { match message { PostComposerInput::SmartSummary(components::SmartSummaryOutput::Start) => { - self.set_smart_summary_busy_guard( - Some(relm4::main_adw_application().mark_busy()) - ); - if self.smart_summary.sender().send(components::SmartSummaryInput::Text( - self.content_buffer.text( + widgets.content_textarea.set_sensitive(false); + if self.content_buffer.char_count() == 0 { + let _ = self.smart_summary.sender().send( + components::SmartSummaryInput::Cancel + ); + } else { + let text = self.content_buffer.text( &self.content_buffer.start_iter(), &self.content_buffer.end_iter(), false - ).into() - )).is_ok() { - self.summary_buffer.set_text(""); + ); + + self.set_smart_summary_busy_guard( + Some(relm4::main_adw_application().mark_busy()) + ); + if self.smart_summary.sender().send( + components::SmartSummaryInput::Text(text.into()) + ).is_ok() { + self.summary_buffer.set_text(""); + } } + widgets.content_textarea.set_sensitive(true); }, PostComposerInput::SmartSummary(components::SmartSummaryOutput::Chunk(text)) => { self.summary_buffer.insert_text(self.summary_buffer.length(), text); -- cgit 1.4.1