From 4ae2dc55d831469ae577a1a88dfda39bac34c949 Mon Sep 17 00:00:00 2001 From: MrLetsplay Date: Sat, 10 Feb 2024 19:39:22 +0100 Subject: [PATCH] Clean up code, Per chat text input box state, Improve UI, Add launch config --- .vscode/launch.json | 45 +++++++++++++++++ src/api.rs | 7 ++- src/main.rs | 119 +++++++++++++++++++++++--------------------- 3 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..215c079 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'icedtest'", + "cargo": { + "args": [ + "build", + "--bin=icedtest", + "--package=icedtest" + ], + "filter": { + "name": "icedtest", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'icedtest'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=icedtest", + "--package=icedtest" + ], + "filter": { + "name": "icedtest", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index 47a45f0..925c06f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -29,7 +29,7 @@ pub struct OllamaChat { pub messages: Vec, #[serde(skip_serializing)] - pub current_message: Option>>, + pub generating_message: Option, } #[derive(Serialize, Deserialize, Clone)] @@ -79,7 +79,7 @@ impl OllamaAPI { api_url: self.api_url.clone(), model: String::from(model), messages: Vec::new(), - current_message: None, + generating_message: None, })); self.chats.push(chat.clone()); @@ -115,8 +115,7 @@ impl OllamaChat { let body = serde_json::to_string(chat.deref())?; println!("Sending: {}", body); - let msg = Arc::new(RwLock::new(String::from(""))); - chat.current_message = Some(msg.clone()); + chat.generating_message = Some(String::new()); } let chat = chat.read().await; diff --git a/src/main.rs b/src/main.rs index 9d54421..80170b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ mod api; use std::{ cell::RefCell, io::{self, Write}, - ops::Deref, sync::{ mpsc::{Receiver, Sender}, Arc, @@ -14,8 +13,10 @@ use api::{ChatMessage, OllamaAPI}; use iced::{ alignment::{Horizontal, Vertical}, futures::StreamExt, + subscription, widget::{button, column, container, row, scrollable, text, text_input}, - window, Application, Color, Command, Length, Settings, Subscription, Theme, + window::{self}, + Application, Color, Command, Event, Length, Settings, Subscription, Theme, }; use tokio::sync::RwLock; @@ -24,13 +25,13 @@ use crate::api::{OllamaChat, Role}; enum UI { Error(String), Chats(UIState), + Closing, } struct UIState { ollama_api: Arc>, active_chat: Option, chat_input: String, - busy: bool, send: Sender, recv: RefCell>>, } @@ -44,8 +45,7 @@ impl UIState { Ok(Self { ollama_api: Arc::new(RwLock::new(OllamaAPI::create(api_url)?)), active_chat: None, - chat_input: String::from(""), - busy: false, + chat_input: String::new(), send, recv: RefCell::new(Some(recv)), }) @@ -55,6 +55,7 @@ impl UIState { #[derive(Debug, Clone)] enum UIMessage { Nop, + EventOccurred(Event), CreateChat, OpenChat(usize), ChatInput(String), @@ -63,10 +64,9 @@ enum UIMessage { } fn main() -> anyhow::Result<()> { - println!("Making request"); - let (send, recv) = std::sync::mpsc::channel(); let mut settings = Settings::with_flags(UIFlags { send, recv }); + settings.exit_on_close_request = false; settings.window = window::Settings { size: (720, 480), ..window::Settings::default() @@ -101,10 +101,19 @@ impl Application for UI { } fn title(&self) -> String { - String::from("Hello World") + String::from("AIGUI") } fn update(&mut self, message: Self::Message) -> iced::Command { + if let UIMessage::EventOccurred(event) = &message { + if let Event::Window(window::Event::CloseRequested) = event { + *self = UI::Closing; + return window::close(); + } else { + return Command::none(); + } + } + match self { UI::Chats(state) => match message { UIMessage::CreateChat => { @@ -132,8 +141,7 @@ impl Application for UI { if let Some(index) = state.active_chat { let content = state.chat_input.clone(); - state.chat_input = String::from(""); - state.busy = true; + state.chat_input = String::new(); let send = state.send.clone(); let ollama_api = state.ollama_api.clone(); return Command::perform( @@ -146,8 +154,7 @@ impl Application for UI { chat = api.chats[index].clone(); let mut chat = chat.write().await; chat.send_user(&content); - chat.current_message = - Some(Arc::new(RwLock::new(String::from("")))); + chat.generating_message = Some(String::new()); } //let mut chat = chat.write().await; @@ -163,10 +170,10 @@ impl Application for UI { let content = content.unwrap(); let chat_arc = chat.clone(); - let chat = chat_arc.read().await; - let msg = chat.current_message.clone(); + let mut chat = chat_arc.write().await; + let msg = &mut chat.generating_message; if let Some(msg) = msg { - msg.write().await.push_str(&content); + msg.push_str(&content); } print!("{}", content); @@ -176,14 +183,12 @@ impl Application for UI { } let mut chat = chat.write().await; - let msg = chat.current_message.clone(); + let msg = &chat.generating_message.clone(); if let Some(msg) = msg { - let msg = msg.write().await; - chat.send_assistant(msg.deref()); - chat.current_message = None; + chat.send_assistant(&msg); + chat.generating_message = None; } } - //state.ollama_api.complete(&x); }, |_| UIMessage::DoneGenerating, ); @@ -191,13 +196,11 @@ impl Application for UI { Command::none() } - UIMessage::DoneGenerating => { - state.busy = false; - Command::none() - } + UIMessage::DoneGenerating => Command::none(), _ => Command::none(), }, UI::Error(_) => Command::none(), + UI::Closing => Command::none(), } } @@ -210,16 +213,13 @@ impl Application for UI { .horizontal_alignment(Horizontal::Center) .into(), UI::Chats(state) => { - println!("Blocking"); let api = state.ollama_api.blocking_read(); - println!("Blocing done!"); - let mut chats: Vec>> = api.chats .iter() .enumerate() .map(|c| { - button("amogus") + button("Chat") .on_press(UIMessage::OpenChat(c.0)) .width(Length::Fill) .into() @@ -234,13 +234,14 @@ impl Application for UI { .into(), ); - let chats_list = container(column(chats).padding(10.0).spacing(10)) + let chats_list = scrollable(container(column(chats).padding(10.0).spacing(10))) .height(Length::Fill) .width(Length::Fixed(300.0)); let mut messages: Vec< iced::Element<'_, Self::Message, iced::Renderer>, >; + let mut input_box = text_input("Your message", &state.chat_input); if let Some(chat_index) = state.active_chat { let chat = &api.chats[chat_index].blocking_read(); messages = chat @@ -249,20 +250,23 @@ impl Application for UI { .map(|x| self.render_message(x)) .collect(); - if let Some(msg) = &chat.current_message { - messages.push(text(msg.blocking_read()).into()); + if let Some(msg) = &chat.generating_message { + let mut msg = msg.clone(); + msg.push_str("..."); + let in_progress_message = ChatMessage { + role: Role::Assistant, + content: msg, + }; + messages.push(self.render_message(&in_progress_message)); + } else { + input_box = input_box + .on_input(|x| UIMessage::ChatInput(x)) + .on_submit(UIMessage::ChatSubmit) } } else { messages = Vec::new(); } - let mut input_box = text_input("Your message", &state.chat_input); - if !state.busy { - input_box = input_box - .on_input(|x| UIMessage::ChatInput(x)) - .on_submit(UIMessage::ChatSubmit) - } - let active_chat = container( column![ scrollable(container(column(messages).spacing(10))) @@ -277,14 +281,8 @@ impl Application for UI { .height(Length::Fill); container(row![chats_list, active_chat]).into() - - /*scrollable( - container(column![content, amogus].spacing(20).max_width(480)) - .padding(40) - .center_x(), - ) - .into()*/ } + UI::Closing => text("Closing").into(), } } @@ -293,22 +291,27 @@ impl Application for UI { } fn subscription(&self) -> iced::Subscription { + let event_subscription = subscription::events().map(UIMessage::EventOccurred); match self { - UI::Chats(state) => iced::subscription::unfold( - "external messages", - state.recv.take(), - move |recv| async move { - let msg = recv.as_ref().unwrap().recv(); + UI::Chats(state) => { + let recv_subscription = iced::subscription::unfold( + "external messages", + state.recv.take(), + move |recv| async move { + let msg = recv.as_ref().unwrap().recv(); - if let Err(_) = msg { - return (UIMessage::Nop, recv); - } + if let Err(_) = msg { + return (UIMessage::Nop, recv); + } - let msg = msg.unwrap(); - (msg, recv) - }, - ), - _ => Subscription::none(), + let msg = msg.unwrap(); + (msg, recv) + }, + ); + + Subscription::batch(vec![event_subscription, recv_subscription]) + } + _ => event_subscription, } } }