summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-08-22 18:30:14 +0300
committerVika <vika@fireburn.ru>2024-08-22 18:30:14 +0300
commit2bd15a7d832bb49c9a5a43eb9475dde1cb4696b3 (patch)
tree9e5eb3ba98a261ff446f79e83f31105e06f0e147
parent7a1b818d1edf5347d8215e164edf77e4210fa4dd (diff)
downloadbowl-2bd15a7d832bb49c9a5a43eb9475dde1cb4696b3.tar.zst
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.
-rw-r--r--src/components/smart_summary.rs65
-rw-r--r--src/lib.rs30
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<String, Error>;
 
     view! {
         #[root]
@@ -52,44 +53,66 @@ impl AsyncComponent for SmartSummaryButton {
         }
     }
 
-    async fn init(
+    fn init(
         _init: Self::Init,
         root: Self::Root,
-        sender: AsyncComponentSender<Self>
-    ) -> AsyncComponentParts<Self> {
+        sender: ComponentSender<Self>
+    ) -> ComponentParts<Self> {
         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<Self>,
+        sender: ComponentSender<Self>,
         _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<Self>, _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<micropub::Client>,
 
-    #[do_not_track] smart_summary: AsyncController<components::SmartSummaryButton>,
+    #[do_not_track] smart_summary: Controller<components::SmartSummaryButton>,
 }
 
 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);