From 5fb49fd56f605626f8a9589de38e1611092d07ec Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Tue, 6 Aug 2024 17:11:40 -0700 Subject: [PATCH 01/51] windows: detect CUDA; use CUDA-enabled WASI-nn plugin --- moxin-runner/src/main.rs | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/moxin-runner/src/main.rs b/moxin-runner/src/main.rs index c73d6e09..b93652fd 100644 --- a/moxin-runner/src/main.rs +++ b/moxin-runner/src/main.rs @@ -135,9 +135,25 @@ const ENV_LD_LIBRARY_PATH: &str = "LD_LIBRARY_PATH"; #[cfg(target_os = "macos")] const ENV_DYLD_FALLBACK_LIBRARY_PATH: &str = "DYLD_FALLBACK_LIBRARY_PATH"; + /// Returns the URL of the WASI-NN plugin that should be downloaded, and its inner directory name. +/// +/// Note that this is only used on Windows, because the install_v2.sh script handles it on Linux. +/// +/// The plugin selection follows this priority order of hardware features: +/// 1. The CUDA build, if CUDA V12 is installed. +/// 2. The default AVX512 build, if on x86_64 and AVX512F is supported. +/// 3. Otherwise, the noavx build (which itself still requires SSE4.2 or SSE4a). #[cfg(windows)] fn wasmedge_wasi_nn_plugin_url() -> (&'static str, &'static str) { + // Currently, WasmEdge's b3499 release only provides a CUDA 12 build for Windows. + if matches!(get_cuda_version(), Some(CudaVersion::V12)) { + return ( + "https://github.com/second-state/WASI-NN-GGML-PLUGIN-REGISTRY/releases/download/b3499/WasmEdge-plugin-wasi_nn-ggml-cuda-0.14.0-windows_x86_64.zip", + "WasmEdge-plugin-wasi_nn-ggml-cuda-0.14.0-windows_x86_64", + ); + } + #[cfg(target_arch = "x86_64")] if is_x86_feature_detected!("avx512f") { return ( @@ -476,6 +492,42 @@ fn wasmedge_root_dir_from_env_vars() -> Option { } } +/// Versions of CUDA that WasmEdge supports. +enum CudaVersion { + /// CUDA Version 12 + V12, + /// CUDA Version 11 + V11, +} + +/// Attempts to discover what version of CUDA is locally installed, if any. +/// +/// This function first runs `nvcc --version` on both Linux and Windows, +/// and if that fails, it will try `/usr/local/cuda/bin/nvcc --version` on Linux only. +fn get_cuda_version() -> Option { + let mut output = Command::new("nvcc") + .arg("--version") + .output(); + + #[cfg(target_os = "linux")] { + output = output.or_else(|_| + Command::new("/usr/local/cuda/bin/nvcc") + .arg("--version") + .output() + ); + } + + let output = output.ok()?; + let output = String::from_utf8_lossy(&output.stdout); + if output.contains("V12") { + Some(CudaVersion::V12) + } else if output.contains("V11") { + Some(CudaVersion::V11) + } else { + None + } +} + /// Runs the `_moxin_app` binary, which must be located in the same directory as this moxin-runner binary. /// /// An optional path to the directory containing the main WasmEdge dylib can be provided, From 28688fc846252716f39b21e4b3eacae5eb11d8ee Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:58:47 -0300 Subject: [PATCH 02/51] model loader external interface changes --- src/chat/chat_panel.rs | 4 +- src/data/chats/mod.rs | 71 ++++++++++++------- src/data/chats/model_loader.rs | 122 +++++++++++++++++++++------------ src/data/store.rs | 6 +- 4 files changed, 129 insertions(+), 74 deletions(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index f19d1aec..5765bd74 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -511,7 +511,7 @@ impl WidgetMatchEvent for ChatPanel { } if let ModelSelectorAction::Selected(downloaded_file) = action.cast() { - store.load_model(&downloaded_file.file); + store.load_model(downloaded_file.file); self.redraw(cx) } @@ -527,7 +527,7 @@ impl WidgetMatchEvent for ChatPanel { store .chats - .create_empty_chat_and_load_file(&downloaded_file.file); + .create_empty_chat_and_load_file(downloaded_file.file); } _ => {} } diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 46561560..1ac27fdb 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -18,12 +18,20 @@ pub struct Chats { pub saved_chats: Vec>, pub loaded_model: Option, - pub model_loader: Option, + pub model_loader: ModelLoader, current_chat_id: Option, chats_dir: PathBuf, } +/// Posible states in which a model can be at runtime. +pub enum ModelStatus { + Unloaded, + Loading, + Loaded, + Failed, +} + impl Chats { pub fn new(backend: Rc) -> Self { Self { @@ -31,11 +39,35 @@ impl Chats { saved_chats: Vec::new(), current_chat_id: None, loaded_model: None, - model_loader: None, + model_loader: ModelLoader::new(), chats_dir: setup_chats_folder(), } } + /// Obtain the loading status for the model asigned to the current chat. + /// If there is no chat selected, or no model assigned to the chat, it will return `None`. + pub fn current_chat_model_loading_status(&self) -> Option { + let current_chat = self.get_current_chat()?.borrow(); + let current_chat_model_id = current_chat.last_used_file_id.as_ref()?; + + let loading_model = self.get_currently_loading_model(); + let loaded_model = self.loaded_model.as_ref(); + + if let Some(loading_model) = loading_model { + if loading_model.id == *current_chat_model_id { + return Some(ModelStatus::Loading); + } + } + + if let Some(loaded_model) = loaded_model { + if loaded_model.id == *current_chat_model_id { + return Some(ModelStatus::Loaded); + } + } + + Some(ModelStatus::Unloaded) + } + pub fn load_chats(&mut self) { let paths = fs::read_dir(&self.chats_dir).unwrap(); @@ -57,37 +89,26 @@ impl Chats { .map(|c| c.borrow().id) } - pub fn load_model(&mut self, file: &File) { + pub fn load_model(&mut self, file: File) { self.cancel_chat_streaming(); - if let Some(loader) = &self.model_loader { - if !loader.complete { - return; - } + if self.model_loader.is_loading() { + return; } - - let loader = ModelLoader::new(file.clone()); - loader.load_model(self.backend.as_ref()); - self.model_loader = Some(loader); + self.model_loader.load(file, self.backend.as_ref()); } pub fn get_currently_loading_model(&self) -> Option<&File> { - self.model_loader - .as_ref() - .filter(|loader| !loader.complete) - .map(|loader| &loader.file) + if self.model_loader.is_loading() { + return self.model_loader.file(); + } + + None } pub fn update_load_model(&mut self) { - let loader = self.model_loader.as_mut(); - if let Some(loader) = loader { - if loader.check_load_response().is_ok() { - self.loaded_model = Some(loader.file.clone()); - } - - if loader.complete { - self.model_loader = None; - } + if self.model_loader.is_loaded() { + self.loaded_model = self.model_loader.file().cloned(); } } @@ -198,7 +219,7 @@ impl Chats { self.saved_chats.push(new_chat); } - pub fn create_empty_chat_and_load_file(&mut self, file: &File) { + pub fn create_empty_chat_and_load_file(&mut self, file: File) { let new_chat = RefCell::new(Chat::new(self.chats_dir.clone())); new_chat.borrow().save(); diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index b43477dd..d19fa8d9 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -1,31 +1,49 @@ -use std::{sync::mpsc::{channel, Receiver, Sender}, thread}; -use anyhow::Result; use makepad_widgets::SignalToUI; use moxin_backend::Backend; -use moxin_protocol::{data::File, protocol::{Command, LoadModelOptions, LoadModelResponse}}; +use moxin_protocol::{ + data::File, + protocol::{Command, LoadModelOptions, LoadModelResponse}, +}; +use std::{ + sync::{mpsc::channel, Arc, Mutex}, + thread, +}; + +/// All posible states in which the loader can be. +#[derive(Debug, Default)] +pub enum ModelLoaderStatus { + #[default] + Unloaded, + Loading, + Loaded, + Failed(anyhow::Error), +} pub struct ModelLoader { - pub complete: bool, - pub file: File, - load_sender: Sender>, - load_receiver: Receiver>, + status: Arc>, + file: Option, } impl ModelLoader { - pub fn new(file: File) -> Self { - let (tx, rx) = channel(); + pub fn new() -> Self { Self { - complete: false, - file, - load_sender: tx, - load_receiver: rx, + status: Default::default(), + file: None, } } - pub fn load_model(&self, backend: &Backend) { + pub fn load(&mut self, file: File, backend: &Backend) { + if self.is_loading() { + panic!("ModelLoader is already loading a model"); + } + + let file_id = file.id.clone(); + self.file = Some(file); + *self.status.lock().unwrap() = ModelLoaderStatus::Loading; + let (tx, rx) = channel(); let cmd = Command::LoadModel( - self.file.id.clone(), + file_id, LoadModelOptions { prompt_template: None, gpu_layers: moxin_protocol::protocol::GPULayers::Max, @@ -39,52 +57,68 @@ impl ModelLoader { }, tx, ); - - let load_model_tx = self.load_sender.clone(); - backend.command_sender.send(cmd).unwrap(); + let status = self.status.clone(); thread::spawn(move || { - if let Ok(response) = rx.recv() { + let response = rx.recv(); + let mut status_lock = status.lock().unwrap(); + + if let Ok(response) = response { match response { Ok(LoadModelResponse::Completed(_)) => { - load_model_tx.send(Ok(())).unwrap(); + *status_lock = ModelLoaderStatus::Loaded; } Ok(_) => { - eprintln!("Error loading model: Unexpected response"); - load_model_tx.send( - Err(anyhow::anyhow!("Error loading model: Unexpected response")) - ).unwrap(); + let msg = "Error loading model: Unexpected response"; + *status_lock = ModelLoaderStatus::Failed(anyhow::anyhow!("{}", msg)); + eprintln!("{}", msg); } Err(err) => { - eprintln!("Error loading model: {:?}", err); - load_model_tx.send(Err(err)).unwrap(); + eprintln!("Error loading model: {:?}", &err); + *status_lock = ModelLoaderStatus::Failed(err); } } } else { - load_model_tx.send( - Err(anyhow::anyhow!("Error loading model")) - ).unwrap(); + *status_lock = ModelLoaderStatus::Failed(anyhow::anyhow!("Error loading model")); } SignalToUI::set_ui_signal(); }); } - pub fn check_load_response(&mut self) -> Result<()> { - for msg in self.load_receiver.try_iter() { - match msg { - Ok(_) => { - self.complete = true; - return Ok(()) - } - Err(err) => { - self.complete = true; - return Err(err.into()) - } - } - }; + pub fn file(&self) -> Option<&File> { + self.file.as_ref() + } - Ok(()) + pub fn read_status(&self, f: impl FnOnce(&ModelLoaderStatus)) { + f(&*self.status.lock().unwrap()); } -} \ No newline at end of file + + pub fn is_loaded(&self) -> bool { + matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Loaded) + } + + pub fn is_loading(&self) -> bool { + matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Loading) + } + + pub fn is_failed(&self) -> bool { + matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Failed(_)) + } + + pub fn is_finished(&self) -> bool { + self.is_loaded() || self.is_failed() + } + + pub fn is_pending(&self) -> bool { + !self.is_finished() + } + + // TODO: Improve + pub fn block_until_finished(&self) { + while self.is_pending() { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } +} diff --git a/src/data/store.rs b/src/data/store.rs index bbecd0e4..d5673bac 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -87,7 +87,7 @@ impl Store { store } - pub fn load_model(&mut self, file: &File) { + pub fn load_model(&mut self, file: File) { self.chats.load_model(file); } @@ -121,7 +121,7 @@ impl Store { .find(|df| df.file.id == *file_id) .map(|df| df.file.clone()) { - let _ = self.load_model(&file); + self.load_model(file); } } } @@ -280,7 +280,7 @@ impl Store { .find(|d| d.file.id == *file_id) .map(|d| d.file.clone()) { - let _ = self.load_model(&file); + self.load_model(file); } } } From 9832f1ddd3393f2343b0553f1f0f0cd0b82b70f2 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:41:26 -0300 Subject: [PATCH 03/51] simplify model loader unused error and make the status clone --- src/data/chats/model_loader.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index d19fa8d9..073aef8d 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -10,13 +10,13 @@ use std::{ }; /// All posible states in which the loader can be. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub enum ModelLoaderStatus { #[default] Unloaded, Loading, Loaded, - Failed(anyhow::Error), + Failed, } pub struct ModelLoader { @@ -71,16 +71,17 @@ impl ModelLoader { } Ok(_) => { let msg = "Error loading model: Unexpected response"; - *status_lock = ModelLoaderStatus::Failed(anyhow::anyhow!("{}", msg)); + *status_lock = ModelLoaderStatus::Failed; eprintln!("{}", msg); } Err(err) => { eprintln!("Error loading model: {:?}", &err); - *status_lock = ModelLoaderStatus::Failed(err); + *status_lock = ModelLoaderStatus::Failed; } } } else { - *status_lock = ModelLoaderStatus::Failed(anyhow::anyhow!("Error loading model")); + eprintln!("Error loading model: Internal communication error"); + *status_lock = ModelLoaderStatus::Failed; } SignalToUI::set_ui_signal(); @@ -104,7 +105,7 @@ impl ModelLoader { } pub fn is_failed(&self) -> bool { - matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Failed(_)) + matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Failed) } pub fn is_finished(&self) -> bool { From ed529c24f9c98d67343c4014337769004b084285 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:09:20 -0300 Subject: [PATCH 04/51] allow model loader to be used from other threads --- src/data/chats/mod.rs | 4 +- src/data/chats/model_loader.rs | 117 +++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 51 deletions(-) diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 1ac27fdb..05b9e98c 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -98,7 +98,7 @@ impl Chats { self.model_loader.load(file, self.backend.as_ref()); } - pub fn get_currently_loading_model(&self) -> Option<&File> { + pub fn get_currently_loading_model(&self) -> Option { if self.model_loader.is_loading() { return self.model_loader.file(); } @@ -108,7 +108,7 @@ impl Chats { pub fn update_load_model(&mut self) { if self.model_loader.is_loaded() { - self.loaded_model = self.model_loader.file().cloned(); + self.loaded_model = self.model_loader.file(); } } diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index 073aef8d..bfd3ff25 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -5,7 +5,10 @@ use moxin_protocol::{ protocol::{Command, LoadModelOptions, LoadModelResponse}, }; use std::{ - sync::{mpsc::channel, Arc, Mutex}, + sync::{ + mpsc::{channel, Receiver}, + Arc, Mutex, + }, thread, }; @@ -19,93 +22,93 @@ pub enum ModelLoaderStatus { Failed, } -pub struct ModelLoader { - status: Arc>, +#[derive(Default)] +struct ModelLoaderInner { + status: ModelLoaderStatus, file: Option, } +/// Unit for handling the non-blocking loading of models across threads. +#[derive(Clone, Default)] +pub struct ModelLoader(Arc>); + impl ModelLoader { pub fn new() -> Self { - Self { - status: Default::default(), - file: None, - } + Self::default() } - pub fn load(&mut self, file: File, backend: &Backend) { + pub fn load(&mut self, file: File, backend: &Backend) -> Receiver> { if self.is_loading() { panic!("ModelLoader is already loading a model"); } - let file_id = file.id.clone(); - self.file = Some(file); - *self.status.lock().unwrap() = ModelLoaderStatus::Loading; - - let (tx, rx) = channel(); - let cmd = Command::LoadModel( - file_id, - LoadModelOptions { - prompt_template: None, - gpu_layers: moxin_protocol::protocol::GPULayers::Max, - use_mlock: false, - rope_freq_scale: 0.0, - rope_freq_base: 0.0, - context_overflow_policy: - moxin_protocol::protocol::ContextOverflowPolicy::StopAtLimit, - n_batch: None, - n_ctx: None, - }, - tx, - ); - backend.command_sender.send(cmd).unwrap(); - - let status = self.status.clone(); + let mut outer_lock = self.0.lock().unwrap(); + outer_lock.file = Some(file.clone()); + outer_lock.status = ModelLoaderStatus::Loading; + + let rx = dispatch_load_command(backend, file.id.clone()); + let inner = self.0.clone(); + let (load_tx, load_rx) = channel(); thread::spawn(move || { let response = rx.recv(); - let mut status_lock = status.lock().unwrap(); + let mut inner_lock = inner.lock().unwrap(); if let Ok(response) = response { match response { Ok(LoadModelResponse::Completed(_)) => { - *status_lock = ModelLoaderStatus::Loaded; + inner_lock.status = ModelLoaderStatus::Loaded; } Ok(_) => { let msg = "Error loading model: Unexpected response"; - *status_lock = ModelLoaderStatus::Failed; + inner_lock.status = ModelLoaderStatus::Failed; eprintln!("{}", msg); } Err(err) => { eprintln!("Error loading model: {:?}", &err); - *status_lock = ModelLoaderStatus::Failed; + inner_lock.status = ModelLoaderStatus::Failed; } } } else { eprintln!("Error loading model: Internal communication error"); - *status_lock = ModelLoaderStatus::Failed; + inner_lock.status = ModelLoaderStatus::Failed; + } + + match inner_lock.status { + ModelLoaderStatus::Loaded => { + let _ = load_tx.send(Ok(())); + } + ModelLoaderStatus::Failed => { + let _ = load_tx.send(Err(())); + } + _ => { + panic!("ModelLoader finished with unexpected status"); + } } SignalToUI::set_ui_signal(); }); + + load_rx } - pub fn file(&self) -> Option<&File> { - self.file.as_ref() + pub fn file(&self) -> Option { + self.0.lock().unwrap().file.clone() } - pub fn read_status(&self, f: impl FnOnce(&ModelLoaderStatus)) { - f(&*self.status.lock().unwrap()); + pub fn status(&self) -> ModelLoaderStatus { + self.0.lock().unwrap().status.clone() } pub fn is_loaded(&self) -> bool { - matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Loaded) + matches!(self.status(), ModelLoaderStatus::Loaded) } pub fn is_loading(&self) -> bool { - matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Loading) + matches!(self.status(), ModelLoaderStatus::Loading) } pub fn is_failed(&self) -> bool { - matches!(*self.status.lock().unwrap(), ModelLoaderStatus::Failed) + matches!(self.status(), ModelLoaderStatus::Failed) } pub fn is_finished(&self) -> bool { @@ -115,11 +118,27 @@ impl ModelLoader { pub fn is_pending(&self) -> bool { !self.is_finished() } +} - // TODO: Improve - pub fn block_until_finished(&self) { - while self.is_pending() { - std::thread::sleep(std::time::Duration::from_millis(100)); - } - } +fn dispatch_load_command( + backend: &Backend, + file_id: String, +) -> Receiver> { + let (tx, rx) = channel(); + let cmd = Command::LoadModel( + file_id, + LoadModelOptions { + prompt_template: None, + gpu_layers: moxin_protocol::protocol::GPULayers::Max, + use_mlock: false, + rope_freq_scale: 0.0, + rope_freq_base: 0.0, + context_overflow_policy: moxin_protocol::protocol::ContextOverflowPolicy::StopAtLimit, + n_batch: None, + n_ctx: None, + }, + tx, + ); + backend.command_sender.send(cmd).unwrap(); + rx } From cd8dbc07c49d2d18544482b6810244e72c988b9b Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:03:45 -0300 Subject: [PATCH 05/51] allow model loader to work without file details --- src/chat/chat_panel.rs | 2 +- src/chat/model_selector.rs | 7 +++---- src/data/chats/mod.rs | 28 +++++++--------------------- src/data/chats/model_loader.rs | 24 +++++++++++++++++------- src/data/downloads/mod.rs | 18 ++++++++++++++++++ src/data/store.rs | 30 +++++++++++++++++++++++++++++- 6 files changed, 75 insertions(+), 34 deletions(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index 5765bd74..6d76e914 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -602,7 +602,7 @@ impl ChatPanel { } else if store.chats.loaded_model.is_none() { State::NoModelSelected } else { - let is_loading = store.chats.get_currently_loading_model().is_some(); + let is_loading = store.get_currently_loading_model().is_some(); store.chats.get_current_chat().map_or( State::ModelSelectedWithEmptyChat { is_loading }, diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index 0b5a6039..f0ace738 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -355,7 +355,7 @@ impl ModelSelector { } fn update_loading_model_state(&mut self, cx: &mut Cx, store: &Store) { - if store.chats.get_currently_loading_model().is_some() { + if store.get_currently_loading_model().is_some() { self.model_selector_loading(id!(loading)) .show_and_animate(cx); } else { @@ -371,7 +371,7 @@ impl ModelSelector { }, ); - if let Some(file) = &store.chats.get_currently_loading_model() { + if let Some(file) = &store.get_currently_loading_model() { // When a model is being loaded, show the "loading state" let caption = format!("Loading {}", file.name); self.view(id!(selected)).apply_over( @@ -449,6 +449,5 @@ fn options_to_display(store: &Store) -> bool { } fn no_active_model(store: &Store) -> bool { - store.get_loaded_downloaded_file().is_none() - && store.chats.get_currently_loading_model().is_none() + store.get_loaded_downloaded_file().is_none() && store.get_currently_loading_model().is_none() } diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 05b9e98c..464b19db 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -50,17 +50,17 @@ impl Chats { let current_chat = self.get_current_chat()?.borrow(); let current_chat_model_id = current_chat.last_used_file_id.as_ref()?; - let loading_model = self.get_currently_loading_model(); - let loaded_model = self.loaded_model.as_ref(); + let loading_model_id = self.model_loader.get_loading_file_id(); + let loaded_model_id = self.loaded_model.as_ref().map(|m| m.id.clone()); - if let Some(loading_model) = loading_model { - if loading_model.id == *current_chat_model_id { + if let Some(loading_model_id) = loading_model_id { + if loading_model_id == *current_chat_model_id { return Some(ModelStatus::Loading); } } - if let Some(loaded_model) = loaded_model { - if loaded_model.id == *current_chat_model_id { + if let Some(loaded_model_id) = loaded_model_id { + if loaded_model_id == *current_chat_model_id { return Some(ModelStatus::Loaded); } } @@ -95,21 +95,7 @@ impl Chats { if self.model_loader.is_loading() { return; } - self.model_loader.load(file, self.backend.as_ref()); - } - - pub fn get_currently_loading_model(&self) -> Option { - if self.model_loader.is_loading() { - return self.model_loader.file(); - } - - None - } - - pub fn update_load_model(&mut self) { - if self.model_loader.is_loaded() { - self.loaded_model = self.model_loader.file(); - } + self.model_loader.load(file.id, self.backend.as_ref()); } pub fn get_current_chat_id(&self) -> Option { diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index bfd3ff25..7a4660ed 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -1,7 +1,7 @@ use makepad_widgets::SignalToUI; use moxin_backend::Backend; use moxin_protocol::{ - data::File, + data::{File, FileID}, protocol::{Command, LoadModelOptions, LoadModelResponse}, }; use std::{ @@ -25,7 +25,7 @@ pub enum ModelLoaderStatus { #[derive(Default)] struct ModelLoaderInner { status: ModelLoaderStatus, - file: Option, + file_id: Option, } /// Unit for handling the non-blocking loading of models across threads. @@ -37,16 +37,16 @@ impl ModelLoader { Self::default() } - pub fn load(&mut self, file: File, backend: &Backend) -> Receiver> { + pub fn load(&mut self, file_id: FileID, backend: &Backend) -> Receiver> { if self.is_loading() { panic!("ModelLoader is already loading a model"); } let mut outer_lock = self.0.lock().unwrap(); - outer_lock.file = Some(file.clone()); + outer_lock.file_id = Some(file_id.clone()); outer_lock.status = ModelLoaderStatus::Loading; - let rx = dispatch_load_command(backend, file.id.clone()); + let rx = dispatch_load_command(backend, file_id.clone()); let inner = self.0.clone(); let (load_tx, load_rx) = channel(); thread::spawn(move || { @@ -91,8 +91,8 @@ impl ModelLoader { load_rx } - pub fn file(&self) -> Option { - self.0.lock().unwrap().file.clone() + pub fn file_id(&self) -> Option { + self.0.lock().unwrap().file_id.clone() } pub fn status(&self) -> ModelLoaderStatus { @@ -118,6 +118,16 @@ impl ModelLoader { pub fn is_pending(&self) -> bool { !self.is_finished() } + + /// Get the file id of the model that is currently being loaded. + /// Returns `None` if the model loader is not at a loading state. + pub fn get_loading_file_id(&self) -> Option { + if self.is_loading() { + return self.file_id(); + } + + None + } } fn dispatch_load_command( diff --git a/src/data/downloads/mod.rs b/src/data/downloads/mod.rs index 83417994..ea459854 100644 --- a/src/data/downloads/mod.rs +++ b/src/data/downloads/mod.rs @@ -110,6 +110,24 @@ impl Downloads { ); } + /// Get a known file. No matter it's status. + pub fn get_file(&self, file_id: &FileID) -> Option<&File> { + // Bet this should not be different things just because they have attached status specific data. + + self.downloaded_files + .iter() + .find(|f| f.file.id == *file_id) + .map(|f| &f.file) + .or_else(|| { + self.pending_downloads + .iter() + .find(|d| d.file.id == *file_id) + .map(|d| &d.file) + }) + // probably unnecessary + // .or_else(|| self.current_downloads.get(file_id).map(|d| &d.file)) + } + pub fn pause_download_file(&mut self, file_id: &FileID) { let Some(current_download) = self.current_downloads.get(file_id) else { return; diff --git a/src/data/store.rs b/src/data/store.rs index d5673bac..b7cdf262 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -92,7 +92,21 @@ impl Store { } fn update_load_model(&mut self) { - self.chats.update_load_model(); + // self.chats.update_load_model(); + if self.chats.model_loader.is_loaded() { + self.chats.loaded_model = self + .chats + .model_loader + .file_id() + .map(|id| { + self.downloads + .downloaded_files + .iter() + .find(|df| df.file.id == id) + .map(|df| df.file.clone()) + }) + .flatten(); + } if let Some(file) = &self.chats.loaded_model { self.preferences.set_current_chat_model(file.id.clone()); @@ -104,6 +118,20 @@ impl Store { } } + pub fn get_currently_loading_model(&self) -> Option { + self.chats + .model_loader + .get_loading_file_id() + .map(|file_id| { + self.downloads + .downloaded_files + .iter() + .find(|df| df.file.id == file_id) + .map(|df| df.file.clone()) + .expect("File being loaded not known?") + }) + } + pub fn select_chat(&mut self, chat_id: ChatID) { self.chats.set_current_chat(chat_id); From f4702158c10b9345723e44768992a6b750e0c1ba Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:57:56 -0300 Subject: [PATCH 06/51] have access to unloded models when sending messages --- src/chat/chat_panel.rs | 8 +++-- src/data/chats/chat.rs | 18 +++++++--- src/data/chats/mod.rs | 41 ----------------------- src/data/store.rs | 74 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 48 deletions(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index 6d76e914..78803b3f 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -538,7 +538,11 @@ impl WidgetMatchEvent for ChatPanel { self.redraw(cx); } ChatLineAction::Edit(id, updated, regenerate) => { - store.chats.edit_chat_message(id, updated, regenerate); + if regenerate { + store.edit_chat_message_regenerating(id, updated) + } else { + store.edit_chat_message(id, updated); + } self.redraw(cx); } _ => {} @@ -762,7 +766,7 @@ impl ChatPanel { } | State::ModelSelectedWithEmptyChat { is_loading: false } ) { let store = scope.data.get_mut::().unwrap(); - store.chats.send_chat_message(prompt.clone()); + store.send_chat_message(prompt.clone()); let prompt_input = self.text_input(id!(main_prompt_input.prompt)); prompt_input.set_text_and_redraw(cx, ""); diff --git a/src/data/chats/chat.rs b/src/data/chats/chat.rs index 71be14f7..d969b99a 100644 --- a/src/data/chats/chat.rs +++ b/src/data/chats/chat.rs @@ -12,6 +12,8 @@ use serde::{Deserialize, Serialize}; use crate::data::filesystem::{read_from_file, write_to_file}; +use super::model_loader::ModelLoader; + pub type ChatID = u128; #[derive(Clone, Debug)] @@ -203,7 +205,13 @@ impl Chat { } } } - pub fn send_message_to_model(&mut self, prompt: String, loaded_file: &File, backend: &Backend) { + pub fn send_message_to_model( + &mut self, + prompt: String, + wanted_file: &File, + model_loader: ModelLoader, + backend: &Backend, + ) { let (tx, rx) = channel(); let mut messages: Vec<_> = self .messages @@ -228,7 +236,7 @@ impl Chat { content: system_prompt.clone(), role: Role::System, name: None, - } + }, ); } else { messages.insert( @@ -245,7 +253,7 @@ impl Chat { let cmd = Command::Chat( ChatRequestData { messages, - model: loaded_file.name.clone(), + model: wanted_file.name.clone(), frequency_penalty: Some(ip.frequency_penalty), logprobs: None, top_logprobs: None, @@ -277,12 +285,12 @@ impl Chat { content: prompt.clone(), }); - self.last_used_file_id = Some(loaded_file.id.clone()); + self.last_used_file_id = Some(wanted_file.id.clone()); self.messages.push(ChatMessage { id: next_id + 1, role: Role::Assistant, - username: Some(loaded_file.name.clone()), + username: Some(wanted_file.name.clone()), content: "".to_string(), }); diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 464b19db..ed149722 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -125,19 +125,6 @@ impl Chats { chat.save(); } - pub fn send_chat_message(&mut self, prompt: String) { - let Some(loaded_model) = self.loaded_model.as_ref() else { - println!("Skip sending message because loaded model not found"); - return; - }; - - if let Some(chat) = self.get_current_chat() { - chat.borrow_mut() - .send_message_to_model(prompt, loaded_model, self.backend.as_ref()); - chat.borrow().save(); - } - } - pub fn cancel_chat_streaming(&mut self) { if let Some(chat) = self.get_current_chat() { chat.borrow_mut().cancel_streaming(self.backend.as_ref()); @@ -151,34 +138,6 @@ impl Chats { } } - pub fn edit_chat_message( - &mut self, - message_id: usize, - updated_message: String, - regenerate: bool, - ) { - if let Some(chat) = &mut self.get_current_chat() { - let mut chat = chat.borrow_mut(); - if regenerate { - if let Some(loaded_model) = self.loaded_model.as_ref() { - if chat.is_streaming { - chat.cancel_streaming(self.backend.as_ref()); - } - - chat.remove_messages_from(message_id); - chat.send_message_to_model( - updated_message, - loaded_model, - self.backend.as_ref(), - ); - } - } else { - chat.edit_message(message_id, updated_message); - } - chat.save(); - } - } - pub fn eject_model(&mut self) -> Result<()> { let (tx, rx) = channel(); self.backend diff --git a/src/data/store.rs b/src/data/store.rs index b7cdf262..7e913867 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -118,6 +118,80 @@ impl Store { } } + pub fn send_chat_message(&mut self, prompt: String) { + if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { + if let Some(file_id) = &chat.last_used_file_id { + if let Some(file) = self + .downloads + .downloaded_files + .iter() + .find(|df| df.file.id == *file_id) + .map(|df| &df.file) + { + chat.send_message_to_model( + prompt, + file, + self.chats.model_loader.clone(), + &self.backend, + ); + chat.save(); + } + } + } + } + + pub fn edit_chat_message(&mut self, message_id: usize, updated_message: String) { + if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { + chat.edit_message(message_id, updated_message); + chat.save(); + } + + // if let Some(chat) = &mut self.get_current_chat() { + // let mut chat = chat.borrow_mut(); + // if regenerate { + // if chat.is_streaming { + // chat.cancel_streaming(self.backend.as_ref()); + // } + + // chat.remove_messages_from(message_id); + // chat.send_message_to_model( + // updated_message, + // file, + // self.model_loader.clone(), + // self.backend.as_ref(), + // ); + // } else { + // chat.edit_message(message_id, updated_message); + // } + // chat.save(); + // } + } + + // Enhancement: Would be ideal to just have a `regenerate_from` function` to be + // used after `edit_chat_message` and keep concerns separated. + pub fn edit_chat_message_regenerating(&mut self, message_id: usize, updated_message: String) { + if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { + if let Some(file_id) = &chat.last_used_file_id { + if let Some(file) = self + .downloads + .downloaded_files + .iter() + .find(|df| df.file.id == *file_id) + .map(|df| &df.file) + { + chat.remove_messages_from(message_id); + chat.send_message_to_model( + updated_message, + file, + self.chats.model_loader.clone(), + &self.backend, + ); + chat.save(); + } + } + } + } + pub fn get_currently_loading_model(&self) -> Option { self.chats .model_loader From 0d829c2ff1c21c030cac046faea85d6c84cccff6 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:20:54 -0300 Subject: [PATCH 07/51] load model when sending message --- src/data/chats/chat.rs | 78 ++++++++++++++++++++-------------- src/data/chats/mod.rs | 3 +- src/data/chats/model_loader.rs | 24 ++++++++--- 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/src/data/chats/chat.rs b/src/data/chats/chat.rs index d969b99a..34d1f90e 100644 --- a/src/data/chats/chat.rs +++ b/src/data/chats/chat.rs @@ -209,7 +209,7 @@ impl Chat { &mut self, prompt: String, wanted_file: &File, - model_loader: ModelLoader, + mut model_loader: ModelLoader, backend: &Backend, ) { let (tx, rx) = channel(); @@ -294,42 +294,56 @@ impl Chat { content: "".to_string(), }); - let store_chat_tx = self.messages_update_sender.clone(); - backend.command_sender.send(cmd).unwrap(); self.is_streaming = true; - thread::spawn(move || loop { - if let Ok(response) = rx.recv() { - match response { - Ok(ChatResponse::ChatResponseChunk(data)) => { - let mut is_done = false; - - let _ = store_chat_tx.send(ChatTokenArrivalAction::AppendDelta( - data.choices[0].delta.content.clone(), - )); - - if let Some(_reason) = &data.choices[0].finish_reason { - is_done = true; - let _ = store_chat_tx.send(ChatTokenArrivalAction::StreamingDone); - } - SignalToUI::set_ui_signal(); - if is_done { + let store_chat_tx = self.messages_update_sender.clone(); + let wanted_file = wanted_file.clone(); + let mut command_sender = backend.command_sender.clone(); + thread::spawn(move || { + if let Err(()) = model_loader + .load(wanted_file.id, command_sender.clone()) + .recv() + .unwrap() + { + return; + } + + command_sender.send(cmd).unwrap(); + + loop { + if let Ok(response) = rx.recv() { + match response { + Ok(ChatResponse::ChatResponseChunk(data)) => { + let mut is_done = false; + + let _ = store_chat_tx.send(ChatTokenArrivalAction::AppendDelta( + data.choices[0].delta.content.clone(), + )); + + if let Some(_reason) = &data.choices[0].finish_reason { + is_done = true; + let _ = store_chat_tx.send(ChatTokenArrivalAction::StreamingDone); + } + + SignalToUI::set_ui_signal(); + if is_done { + break; + } + } + Ok(ChatResponse::ChatFinalResponseData(data)) => { + let _ = store_chat_tx.send(ChatTokenArrivalAction::AppendDelta( + data.choices[0].message.content.clone(), + )); + let _ = store_chat_tx.send(ChatTokenArrivalAction::StreamingDone); + SignalToUI::set_ui_signal(); break; } + Err(err) => eprintln!("Error receiving response chunk: {:?}", err), } - Ok(ChatResponse::ChatFinalResponseData(data)) => { - let _ = store_chat_tx.send(ChatTokenArrivalAction::AppendDelta( - data.choices[0].message.content.clone(), - )); - let _ = store_chat_tx.send(ChatTokenArrivalAction::StreamingDone); - SignalToUI::set_ui_signal(); - break; - } - Err(err) => eprintln!("Error receiving response chunk: {:?}", err), - } - } else { - break; - }; + } else { + break; + }; + } }); self.update_title_based_on_first_message(); diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index ed149722..7d6fdbac 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -95,7 +95,8 @@ impl Chats { if self.model_loader.is_loading() { return; } - self.model_loader.load(file.id, self.backend.as_ref()); + self.model_loader + .load(file.id, self.backend.command_sender.clone()); } pub fn get_current_chat_id(&self) -> Option { diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index 7a4660ed..eef657f5 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -6,7 +6,7 @@ use moxin_protocol::{ }; use std::{ sync::{ - mpsc::{channel, Receiver}, + mpsc::{channel, Receiver, Sender}, Arc, Mutex, }, thread, @@ -37,18 +37,30 @@ impl ModelLoader { Self::default() } - pub fn load(&mut self, file_id: FileID, backend: &Backend) -> Receiver> { + pub fn load( + &mut self, + file_id: FileID, + command_sender: Sender, + ) -> Receiver> { if self.is_loading() { panic!("ModelLoader is already loading a model"); } + let (load_tx, load_rx) = channel(); + + if let Some(prev_file_id) = self.file_id() { + if prev_file_id == file_id { + let _ = load_tx.send(Ok(())); + return load_rx; + } + } + let mut outer_lock = self.0.lock().unwrap(); outer_lock.file_id = Some(file_id.clone()); outer_lock.status = ModelLoaderStatus::Loading; - let rx = dispatch_load_command(backend, file_id.clone()); + let rx = dispatch_load_command(command_sender, file_id.clone()); let inner = self.0.clone(); - let (load_tx, load_rx) = channel(); thread::spawn(move || { let response = rx.recv(); let mut inner_lock = inner.lock().unwrap(); @@ -131,7 +143,7 @@ impl ModelLoader { } fn dispatch_load_command( - backend: &Backend, + command_sender: Sender, file_id: String, ) -> Receiver> { let (tx, rx) = channel(); @@ -149,6 +161,6 @@ fn dispatch_load_command( }, tx, ); - backend.command_sender.send(cmd).unwrap(); + command_sender.send(cmd).unwrap(); rx } From c3304d7e81734a409f73d9a201abe3c64ac43d4f Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:04:20 -0300 Subject: [PATCH 08/51] save used model immediately --- src/data/chats/chat.rs | 4 +--- src/data/chats/mod.rs | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/data/chats/chat.rs b/src/data/chats/chat.rs index 34d1f90e..b3962584 100644 --- a/src/data/chats/chat.rs +++ b/src/data/chats/chat.rs @@ -285,8 +285,6 @@ impl Chat { content: prompt.clone(), }); - self.last_used_file_id = Some(wanted_file.id.clone()); - self.messages.push(ChatMessage { id: next_id + 1, role: Role::Assistant, @@ -298,7 +296,7 @@ impl Chat { let store_chat_tx = self.messages_update_sender.clone(); let wanted_file = wanted_file.clone(); - let mut command_sender = backend.command_sender.clone(); + let command_sender = backend.command_sender.clone(); thread::spawn(move || { if let Err(()) = model_loader .load(wanted_file.id, command_sender.clone()) diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 7d6fdbac..6602701d 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -95,6 +95,10 @@ impl Chats { if self.model_loader.is_loading() { return; } + if let Some(mut chat) = self.get_current_chat().map(|c| c.borrow_mut()) { + chat.last_used_file_id = Some(file.id.clone()); + chat.save(); + } self.model_loader .load(file.id, self.backend.command_sender.clone()); } From ac8f590ae13299b897581e14295d937aa4021a2e Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:10:21 -0300 Subject: [PATCH 09/51] unused declarations --- src/data/chats/model_loader.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index eef657f5..59360e4d 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -1,7 +1,6 @@ use makepad_widgets::SignalToUI; -use moxin_backend::Backend; use moxin_protocol::{ - data::{File, FileID}, + data::FileID, protocol::{Command, LoadModelOptions, LoadModelResponse}, }; use std::{ From 62321cfe4ffa1abf8ccb637ebc0102deaba8767e Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:31:42 -0300 Subject: [PATCH 10/51] do not load model on chat change --- src/chat/chat_history_card.rs | 2 +- src/data/store.rs | 31 +------------------------------ 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/chat/chat_history_card.rs b/src/chat/chat_history_card.rs index 899617b3..d1f84bfa 100644 --- a/src/chat/chat_history_card.rs +++ b/src/chat/chat_history_card.rs @@ -314,7 +314,7 @@ impl WidgetMatchEvent for ChatHistoryCard { ChatHistoryCardAction::ChatSelected(self.chat_id), ); let store = scope.data.get_mut::().unwrap(); - store.select_chat(self.chat_id); + store.chats.set_current_chat(self.chat_id); self.redraw(cx); } } diff --git a/src/data/store.rs b/src/data/store.rs index 7e913867..ab531168 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -206,35 +206,6 @@ impl Store { }) } - pub fn select_chat(&mut self, chat_id: ChatID) { - self.chats.set_current_chat(chat_id); - - if let Some(file_id) = self.get_last_used_file_id_in_current_chat() { - if self - .chats - .loaded_model - .as_ref() - .map_or(true, |m| *m.id != file_id) - { - if let Some(file) = self - .downloads - .downloaded_files - .iter() - .find(|df| df.file.id == *file_id) - .map(|df| df.file.clone()) - { - self.load_model(file); - } - } - } - } - - pub fn get_last_used_file_id_in_current_chat(&self) -> Option { - self.chats - .get_current_chat() - .map(|chat| chat.borrow().last_used_file_id.clone())? - } - pub fn get_loaded_downloaded_file(&self) -> Option { if let Some(file) = &self.chats.loaded_model { self.downloads @@ -367,7 +338,7 @@ impl Store { fn init_current_chat(&mut self) { if let Some(chat_id) = self.chats.get_last_selected_chat_id() { - self.select_chat(chat_id); + self.chats.set_current_chat(chat_id); } else { self.chats.create_empty_chat(); } From 54724b14d45eb3447e6e8dc6aae45d26c766cc8a Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:18:37 -0300 Subject: [PATCH 11/51] display chat last used model --- src/chat/model_selector.rs | 86 +++++++++++++++++++------------------- src/data/downloads/mod.rs | 15 +++++++ 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index f0ace738..cd178d5a 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -3,6 +3,7 @@ use crate::{ shared::{actions::ChatAction, utils::format_model_size}, }; use makepad_widgets::*; +use moxin_protocol::data::{File, Model}; use super::{ model_selector_list::{ModelSelectorAction, ModelSelectorListWidgetExt}, @@ -364,6 +365,39 @@ impl ModelSelector { } fn update_selected_model_info(&mut self, cx: &mut Cx, store: &Store) { + let is_loading = store.chats.model_loader.is_loading(); + + let file = store + .chats + .get_current_chat() + .and_then(|c| c.borrow().last_used_file_id.clone()) + .and_then(|file_id| store.downloads.get_file(&file_id).cloned()) + .or_else(|| store.chats.loaded_model.clone()); + + let model = file + .as_ref() + .map(|f| store.downloads.get_model_by_file_id(&f.id).cloned()) + .flatten(); + + let file_name = file.as_ref().map(|f| f.name.trim()).unwrap_or(""); + let architecture = model.as_ref().map(|m| m.architecture.trim()).unwrap_or(""); + let params_size = model.as_ref().map(|m| m.size.trim()).unwrap_or(""); + let file_size = file + .as_ref() + .map(|f| format_model_size(f.size.trim()).ok()) + .flatten() + .unwrap_or("".into()); + + let is_architecture_visible = !architecture.is_empty() && !is_loading; + let is_params_size_visible = !params_size.is_empty() && !is_loading; + let is_file_size_visible = !file_size.is_empty() && !is_loading; + + let caption = if is_loading { + format!("Loading {}", file_name) + } else { + file_name.to_string() + }; + self.view(id!(choose)).apply_over( cx, live! { @@ -371,48 +405,16 @@ impl ModelSelector { }, ); - if let Some(file) = &store.get_currently_loading_model() { - // When a model is being loaded, show the "loading state" - let caption = format!("Loading {}", file.name); - self.view(id!(selected)).apply_over( - cx, - live! { - visible: true - label = { text: (caption) } - architecture_tag = { visible: false } - params_size_tag = { visible: false } - file_size_tag = { visible: false } - }, - ); - } else { - let Some(downloaded_file) = store.get_loaded_downloaded_file() else { - error!("Error displaying current loaded model"); - return; - }; - - // When a model is loaded, show the model info - let filename = downloaded_file.file.name; - - let architecture = downloaded_file.model.architecture; - let architecture_visible = !architecture.trim().is_empty(); - - let param_size = downloaded_file.model.size; - let param_size_visible = !param_size.trim().is_empty(); - - let size = format_model_size(&downloaded_file.file.size).unwrap_or("".to_string()); - let size_visible = !size.trim().is_empty(); - - self.view(id!(selected)).apply_over( - cx, - live! { - visible: true - label = { text: (filename) } - architecture_tag = { visible: (architecture_visible), caption = { text: (architecture) }} - params_size_tag = { visible: (param_size_visible), caption = { text: (param_size) }} - file_size_tag = { visible: (size_visible), caption = { text: (size) }} - }, - ); - } + self.view(id!(selected)).apply_over( + cx, + live! { + visible: true + label = { text: (caption) } + architecture_tag = { visible: (is_architecture_visible), caption = { text: (architecture) }} + params_size_tag = { visible: (is_params_size_visible), caption = { text: (params_size) }} + file_size_tag = { visible: (is_file_size_visible), caption = { text: (file_size) }} + }, + ); self.redraw(cx); } diff --git a/src/data/downloads/mod.rs b/src/data/downloads/mod.rs index ea459854..45c27593 100644 --- a/src/data/downloads/mod.rs +++ b/src/data/downloads/mod.rs @@ -128,6 +128,21 @@ impl Downloads { // .or_else(|| self.current_downloads.get(file_id).map(|d| &d.file)) } + /// Get a known model. No matter the status of it's related file. + pub fn get_model_by_file_id(&self, file_id: &FileID) -> Option<&Model> { + self.downloaded_files + .iter() + .find(|f| f.file.id == *file_id) + .map(|f| &f.model) + .or_else(|| { + self.pending_downloads + .iter() + .find(|d| d.file.id == *file_id) + .map(|d| &d.model) + }) + // .or_else(|| self.current_downloads.get(file_id).map(|d| &d.model)) + } + pub fn pause_download_file(&mut self, file_id: &FileID) { let Some(current_download) = self.current_downloads.get(file_id) else { return; From 2125fa5fc2a83d2c911bf1842a6e6f823e8d6daf Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:02:33 -0300 Subject: [PATCH 12/51] grey out unloaded model --- src/chat/model_selector.rs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index cd178d5a..1273b1e8 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -366,13 +366,14 @@ impl ModelSelector { fn update_selected_model_info(&mut self, cx: &mut Cx, store: &Store) { let is_loading = store.chats.model_loader.is_loading(); + let loaded_file = store.chats.loaded_model.as_ref(); let file = store .chats .get_current_chat() .and_then(|c| c.borrow().last_used_file_id.clone()) .and_then(|file_id| store.downloads.get_file(&file_id).cloned()) - .or_else(|| store.chats.loaded_model.clone()); + .or_else(|| loaded_file.cloned()); let model = file .as_ref() @@ -398,6 +399,21 @@ impl ModelSelector { file_name.to_string() }; + let is_loaded_file = match (file, loaded_file) { + (Some(f), Some(lf)) => f.id == lf.id, + (None, None) => true, + _ => false, + }; + + let text_enabled_color = hex_rgb_to_vec4_color(0x000000); + let text_disabled_color = hex_rgb_to_vec4_color(0x667085); + + let text_color = if is_loaded_file { + text_enabled_color + } else { + text_disabled_color + }; + self.view(id!(choose)).apply_over( cx, live! { @@ -409,10 +425,10 @@ impl ModelSelector { cx, live! { visible: true - label = { text: (caption) } - architecture_tag = { visible: (is_architecture_visible), caption = { text: (architecture) }} - params_size_tag = { visible: (is_params_size_visible), caption = { text: (params_size) }} - file_size_tag = { visible: (is_file_size_visible), caption = { text: (file_size) }} + label = { text: (caption), draw_text: { color: (text_color) }} + architecture_tag = { visible: (is_architecture_visible), caption = { text: (architecture), draw_text: { color: (text_color) }}} + params_size_tag = { visible: (is_params_size_visible), caption = { text: (params_size), draw_text: { color: (text_color) }}} + file_size_tag = { visible: (is_file_size_visible), caption = { text: (file_size), draw_text: { color: (text_color) }}} }, ); @@ -453,3 +469,15 @@ fn options_to_display(store: &Store) -> bool { fn no_active_model(store: &Store) -> bool { store.get_loaded_downloaded_file().is_none() && store.get_currently_loading_model().is_none() } + +fn rgb_to_vec4_color(r: u8, g: u8, b: u8) -> Vec4 { + vec4(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) +} + +fn hex_rgb_to_vec4_color(hex: u32) -> Vec4 { + let r = ((hex >> 16) & 0xFF) as u8; + let g = ((hex >> 8) & 0xFF) as u8; + let b = (hex & 0xFF) as u8; + + rgb_to_vec4_color(r, g, b) +} From fe9b45425169ef2b16fa8d0ab4419e125e7c912d Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:18:03 -0300 Subject: [PATCH 13/51] on model change, record used model but don't load it --- src/chat/chat_panel.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index 78803b3f..27f2d9bb 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -511,7 +511,10 @@ impl WidgetMatchEvent for ChatPanel { } if let ModelSelectorAction::Selected(downloaded_file) = action.cast() { - store.load_model(downloaded_file.file); + if let Some(chat) = store.chats.get_current_chat() { + chat.borrow_mut().last_used_file_id = Some(downloaded_file.file.id.clone()); + chat.borrow().save(); + } self.redraw(cx) } From 4fa78793c212112e5a1860c319144afb4a3eae31 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:12:33 -0300 Subject: [PATCH 14/51] blocking loading and nice error reporting --- src/data/chats/chat.rs | 7 +- src/data/chats/model_loader.rs | 138 +++++++++++++++++++++------------ 2 files changed, 89 insertions(+), 56 deletions(-) diff --git a/src/data/chats/chat.rs b/src/data/chats/chat.rs index b3962584..6adf5edf 100644 --- a/src/data/chats/chat.rs +++ b/src/data/chats/chat.rs @@ -298,11 +298,8 @@ impl Chat { let wanted_file = wanted_file.clone(); let command_sender = backend.command_sender.clone(); thread::spawn(move || { - if let Err(()) = model_loader - .load(wanted_file.id, command_sender.clone()) - .recv() - .unwrap() - { + if let Err(err) = model_loader.load_blocking(wanted_file.id, command_sender.clone()) { + eprintln!("Error loading model: {}", err); return; } diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index 59360e4d..62ced85f 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use makepad_widgets::SignalToUI; use moxin_protocol::{ data::FileID, @@ -11,6 +12,38 @@ use std::{ thread, }; +/// Immutable handleable error type for the model loader. +/// +/// Actually, just a wrapper around `anyhow::Error` to support `Clone`. +#[derive(Debug, Clone)] +pub struct ModelLoaderError(Arc); + +impl std::error::Error for ModelLoaderError {} + +impl std::fmt::Display for ModelLoaderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ModelLoaderError: {}", self.0) + } +} + +impl From for ModelLoaderError { + fn from(err: anyhow::Error) -> Self { + Self(Arc::new(err)) + } +} + +impl From for ModelLoaderError { + fn from(err: String) -> Self { + Self(Arc::new(anyhow!(err))) + } +} + +impl From<&'static str> for ModelLoaderError { + fn from(err: &'static str) -> Self { + Self(Arc::new(anyhow!(err))) + } +} + /// All posible states in which the loader can be. #[derive(Debug, Default, Clone)] pub enum ModelLoaderStatus { @@ -18,7 +51,7 @@ pub enum ModelLoaderStatus { Unloaded, Loading, Loaded, - Failed, + Failed(#[allow(dead_code)] ModelLoaderError), } #[derive(Default)] @@ -36,70 +69,73 @@ impl ModelLoader { Self::default() } - pub fn load( + pub fn load_blocking( &mut self, file_id: FileID, command_sender: Sender, - ) -> Receiver> { - if self.is_loading() { - panic!("ModelLoader is already loading a model"); - } - - let (load_tx, load_rx) = channel(); - - if let Some(prev_file_id) = self.file_id() { - if prev_file_id == file_id { - let _ = load_tx.send(Ok(())); - return load_rx; + ) -> Result<(), ModelLoaderError> { + match self.status() { + ModelLoaderStatus::Loading => { + return Err(anyhow!("ModelLoader is already loading a model").into()); } - } - - let mut outer_lock = self.0.lock().unwrap(); - outer_lock.file_id = Some(file_id.clone()); - outer_lock.status = ModelLoaderStatus::Loading; - - let rx = dispatch_load_command(command_sender, file_id.clone()); - let inner = self.0.clone(); - thread::spawn(move || { - let response = rx.recv(); - let mut inner_lock = inner.lock().unwrap(); - - if let Ok(response) = response { - match response { - Ok(LoadModelResponse::Completed(_)) => { - inner_lock.status = ModelLoaderStatus::Loaded; - } - Ok(_) => { - let msg = "Error loading model: Unexpected response"; - inner_lock.status = ModelLoaderStatus::Failed; - eprintln!("{}", msg); - } - Err(err) => { - eprintln!("Error loading model: {:?}", &err); - inner_lock.status = ModelLoaderStatus::Failed; + ModelLoaderStatus::Loaded => { + if let Some(prev_file_id) = self.file_id() { + if prev_file_id == file_id { + return Ok(()); } } - } else { - eprintln!("Error loading model: Internal communication error"); - inner_lock.status = ModelLoaderStatus::Failed; } + _ => {} + }; - match inner_lock.status { - ModelLoaderStatus::Loaded => { - let _ = load_tx.send(Ok(())); + self.set_status(ModelLoaderStatus::Loading); + self.set_file_id(Some(file_id.clone())); + + let response = dispatch_load_command(command_sender, file_id.clone()).recv(); + + let result = if let Ok(response) = response { + match response { + Ok(LoadModelResponse::Completed(_)) => { + self.set_status(ModelLoaderStatus::Loaded); + Ok(()) } - ModelLoaderStatus::Failed => { - let _ = load_tx.send(Err(())); + Ok(response) => { + let msg = format!("Unexpected response: {:?}", response); + let err = ModelLoaderError::from(msg); + self.set_status(ModelLoaderStatus::Failed(err.clone())); + Err(err) } - _ => { - panic!("ModelLoader finished with unexpected status"); + Err(err) => { + let err = ModelLoaderError::from(err); + self.set_status(ModelLoaderStatus::Failed(err.clone())); + Err(err) } } + } else { + let err = ModelLoaderError::from("Internal communication error"); + self.set_status(ModelLoaderStatus::Failed(err.clone())); + Err(err) + }; + + SignalToUI::set_ui_signal(); + result + } - SignalToUI::set_ui_signal(); + pub fn load(&mut self, file_id: FileID, command_sender: Sender) { + let mut self_clone = self.clone(); + thread::spawn(move || { + if let Err(err) = self_clone.load_blocking(file_id, command_sender) { + eprintln!("Error loading model: {}", err); + } }); + } + + fn set_status(&mut self, status: ModelLoaderStatus) { + self.0.lock().unwrap().status = status; + } - load_rx + fn set_file_id(&mut self, file_id: Option) { + self.0.lock().unwrap().file_id = file_id; } pub fn file_id(&self) -> Option { @@ -119,7 +155,7 @@ impl ModelLoader { } pub fn is_failed(&self) -> bool { - matches!(self.status(), ModelLoaderStatus::Failed) + matches!(self.status(), ModelLoaderStatus::Failed(_)) } pub fn is_finished(&self) -> bool { From 078672ff2134753a12f198d209046c5e4451c474 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:34:35 -0300 Subject: [PATCH 15/51] details --- src/chat/chat_panel.rs | 2 +- src/chat/model_selector.rs | 15 +++++----- src/data/chats/mod.rs | 32 -------------------- src/data/store.rs | 60 +++++--------------------------------- 4 files changed, 16 insertions(+), 93 deletions(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index 27f2d9bb..d23a804d 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -609,7 +609,7 @@ impl ChatPanel { } else if store.chats.loaded_model.is_none() { State::NoModelSelected } else { - let is_loading = store.get_currently_loading_model().is_some(); + let is_loading = store.chats.model_loader.is_loading(); store.chats.get_current_chat().map_or( State::ModelSelectedWithEmptyChat { is_loading }, diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index 1273b1e8..3cc8fa21 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -3,7 +3,6 @@ use crate::{ shared::{actions::ChatAction, utils::format_model_size}, }; use makepad_widgets::*; -use moxin_protocol::data::{File, Model}; use super::{ model_selector_list::{ModelSelectorAction, ModelSelectorListWidgetExt}, @@ -356,7 +355,7 @@ impl ModelSelector { } fn update_loading_model_state(&mut self, cx: &mut Cx, store: &Store) { - if store.get_currently_loading_model().is_some() { + if store.chats.model_loader.is_loading() { self.model_selector_loading(id!(loading)) .show_and_animate(cx); } else { @@ -405,8 +404,8 @@ impl ModelSelector { _ => false, }; - let text_enabled_color = hex_rgb_to_vec4_color(0x000000); - let text_disabled_color = hex_rgb_to_vec4_color(0x667085); + let text_enabled_color = hex_rgb_color(0x000000); + let text_disabled_color = hex_rgb_color(0x667085); let text_color = if is_loaded_file { text_enabled_color @@ -467,17 +466,17 @@ fn options_to_display(store: &Store) -> bool { } fn no_active_model(store: &Store) -> bool { - store.get_loaded_downloaded_file().is_none() && store.get_currently_loading_model().is_none() + store.get_loaded_downloaded_file().is_none() && store.get_loading_file().is_none() } -fn rgb_to_vec4_color(r: u8, g: u8, b: u8) -> Vec4 { +fn rgb_color(r: u8, g: u8, b: u8) -> Vec4 { vec4(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) } -fn hex_rgb_to_vec4_color(hex: u32) -> Vec4 { +fn hex_rgb_color(hex: u32) -> Vec4 { let r = ((hex >> 16) & 0xFF) as u8; let g = ((hex >> 8) & 0xFF) as u8; let b = (hex & 0xFF) as u8; - rgb_to_vec4_color(r, g, b) + rgb_color(r, g, b) } diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 6602701d..086630e9 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -24,14 +24,6 @@ pub struct Chats { chats_dir: PathBuf, } -/// Posible states in which a model can be at runtime. -pub enum ModelStatus { - Unloaded, - Loading, - Loaded, - Failed, -} - impl Chats { pub fn new(backend: Rc) -> Self { Self { @@ -44,30 +36,6 @@ impl Chats { } } - /// Obtain the loading status for the model asigned to the current chat. - /// If there is no chat selected, or no model assigned to the chat, it will return `None`. - pub fn current_chat_model_loading_status(&self) -> Option { - let current_chat = self.get_current_chat()?.borrow(); - let current_chat_model_id = current_chat.last_used_file_id.as_ref()?; - - let loading_model_id = self.model_loader.get_loading_file_id(); - let loaded_model_id = self.loaded_model.as_ref().map(|m| m.id.clone()); - - if let Some(loading_model_id) = loading_model_id { - if loading_model_id == *current_chat_model_id { - return Some(ModelStatus::Loading); - } - } - - if let Some(loaded_model_id) = loaded_model_id { - if loaded_model_id == *current_chat_model_id { - return Some(ModelStatus::Loaded); - } - } - - Some(ModelStatus::Unloaded) - } - pub fn load_chats(&mut self) { let paths = fs::read_dir(&self.chats_dir).unwrap(); diff --git a/src/data/store.rs b/src/data/store.rs index ab531168..2d0232a4 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -92,20 +92,14 @@ impl Store { } fn update_load_model(&mut self) { - // self.chats.update_load_model(); if self.chats.model_loader.is_loaded() { self.chats.loaded_model = self .chats .model_loader .file_id() - .map(|id| { - self.downloads - .downloaded_files - .iter() - .find(|df| df.file.id == id) - .map(|df| df.file.clone()) - }) - .flatten(); + .map(|id| self.downloads.get_file(&id)) + .flatten() + .cloned(); } if let Some(file) = &self.chats.loaded_model { @@ -121,13 +115,7 @@ impl Store { pub fn send_chat_message(&mut self, prompt: String) { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { if let Some(file_id) = &chat.last_used_file_id { - if let Some(file) = self - .downloads - .downloaded_files - .iter() - .find(|df| df.file.id == *file_id) - .map(|df| &df.file) - { + if let Some(file) = self.downloads.get_file(file_id) { chat.send_message_to_model( prompt, file, @@ -145,26 +133,6 @@ impl Store { chat.edit_message(message_id, updated_message); chat.save(); } - - // if let Some(chat) = &mut self.get_current_chat() { - // let mut chat = chat.borrow_mut(); - // if regenerate { - // if chat.is_streaming { - // chat.cancel_streaming(self.backend.as_ref()); - // } - - // chat.remove_messages_from(message_id); - // chat.send_message_to_model( - // updated_message, - // file, - // self.model_loader.clone(), - // self.backend.as_ref(), - // ); - // } else { - // chat.edit_message(message_id, updated_message); - // } - // chat.save(); - // } } // Enhancement: Would be ideal to just have a `regenerate_from` function` to be @@ -172,13 +140,7 @@ impl Store { pub fn edit_chat_message_regenerating(&mut self, message_id: usize, updated_message: String) { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { if let Some(file_id) = &chat.last_used_file_id { - if let Some(file) = self - .downloads - .downloaded_files - .iter() - .find(|df| df.file.id == *file_id) - .map(|df| &df.file) - { + if let Some(file) = self.downloads.get_file(file_id) { chat.remove_messages_from(message_id); chat.send_message_to_model( updated_message, @@ -192,18 +154,12 @@ impl Store { } } - pub fn get_currently_loading_model(&self) -> Option { + pub fn get_loading_file(&self) -> Option<&File> { self.chats .model_loader .get_loading_file_id() - .map(|file_id| { - self.downloads - .downloaded_files - .iter() - .find(|df| df.file.id == file_id) - .map(|df| df.file.clone()) - .expect("File being loaded not known?") - }) + .map(|file_id| self.downloads.get_file(&file_id)) + .flatten() } pub fn get_loaded_downloaded_file(&self) -> Option { From cba8bd6e58b4aa1de6cd0c30b32249487a5d69f0 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:14:56 -0300 Subject: [PATCH 16/51] more imperative update_selected_model_info --- src/chat/model_selector.rs | 98 +++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 54 deletions(-) diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index 3cc8fa21..b36d6201 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -48,7 +48,7 @@ live_design! { cursor: Hand, - content = { + content = { width: Fill, height: Fit, flow: Overlay, @@ -239,7 +239,8 @@ impl Widget for ModelSelector { .apply_over(cx, live! {height: (height)}); let rotate_angle = self.rotate_animation_progress * std::f64::consts::PI; - self.view(id!(icon_drop.icon)).apply_over(cx, live! {draw_bg: {rotation: (rotate_angle)}}); + self.view(id!(icon_drop.icon)) + .apply_over(cx, live! {draw_bg: {rotation: (rotate_angle)}}); self.redraw(cx); } @@ -349,7 +350,8 @@ impl ModelSelector { fn hide_options(&mut self, cx: &mut Cx) { self.open = false; self.view(id!(options)).apply_over(cx, live! { height: 0 }); - self.view(id!(icon_drop.icon)).apply_over(cx, live! {draw_bg: {rotation: (0.0)}}); + self.view(id!(icon_drop.icon)) + .apply_over(cx, live! {draw_bg: {rotation: (0.0)}}); self.animator_cut(cx, id!(open.hide)); self.redraw(cx); } @@ -364,6 +366,8 @@ impl ModelSelector { } fn update_selected_model_info(&mut self, cx: &mut Cx, store: &Store) { + self.view(id!(choose)).set_visible(false); + let is_loading = store.chats.model_loader.is_loading(); let loaded_file = store.chats.loaded_model.as_ref(); @@ -374,62 +378,48 @@ impl ModelSelector { .and_then(|file_id| store.downloads.get_file(&file_id).cloned()) .or_else(|| loaded_file.cloned()); - let model = file - .as_ref() - .map(|f| store.downloads.get_model_by_file_id(&f.id).cloned()) - .flatten(); - - let file_name = file.as_ref().map(|f| f.name.trim()).unwrap_or(""); - let architecture = model.as_ref().map(|m| m.architecture.trim()).unwrap_or(""); - let params_size = model.as_ref().map(|m| m.size.trim()).unwrap_or(""); - let file_size = file - .as_ref() - .map(|f| format_model_size(f.size.trim()).ok()) - .flatten() - .unwrap_or("".into()); - - let is_architecture_visible = !architecture.is_empty() && !is_loading; - let is_params_size_visible = !params_size.is_empty() && !is_loading; - let is_file_size_visible = !file_size.is_empty() && !is_loading; - - let caption = if is_loading { - format!("Loading {}", file_name) - } else { - file_name.to_string() - }; + if let Some(file) = file { + let selected_view = self.view(id!(selected)); + selected_view.set_visible(true); - let is_loaded_file = match (file, loaded_file) { - (Some(f), Some(lf)) => f.id == lf.id, - (None, None) => true, - _ => false, - }; + let text_color = if Some(&file.id) == loaded_file.map(|f| &f.id) { + hex_rgb_color(0x000000) + } else { + hex_rgb_color(0x667085) + }; - let text_enabled_color = hex_rgb_color(0x000000); - let text_disabled_color = hex_rgb_color(0x667085); + let caption = if is_loading { + format!("Loading {}", file.name.trim()) + } else { + file.name.trim().to_string() + }; - let text_color = if is_loaded_file { - text_enabled_color - } else { - text_disabled_color - }; + let file_size = format_model_size(file.size.trim()).unwrap_or("".into()); + let is_file_size_visible = !file_size.is_empty() && !is_loading; - self.view(id!(choose)).apply_over( - cx, - live! { - visible: false - }, - ); + selected_view.apply_over( + cx, + live! { + label = { text: (caption), draw_text: { color: (text_color) }} + file_size_tag = { visible: (is_file_size_visible), caption = { text: (file_size), draw_text: { color: (text_color) }}} + }, + ); - self.view(id!(selected)).apply_over( - cx, - live! { - visible: true - label = { text: (caption), draw_text: { color: (text_color) }} - architecture_tag = { visible: (is_architecture_visible), caption = { text: (architecture), draw_text: { color: (text_color) }}} - params_size_tag = { visible: (is_params_size_visible), caption = { text: (params_size), draw_text: { color: (text_color) }}} - file_size_tag = { visible: (is_file_size_visible), caption = { text: (file_size), draw_text: { color: (text_color) }}} - }, - ); + if let Some(model) = store.downloads.get_model_by_file_id(&file.id) { + let architecture = model.architecture.trim(); + let params_size = model.size.trim(); + let is_architecture_visible = !architecture.is_empty() && !is_loading; + let is_params_size_visible = !params_size.is_empty() && !is_loading; + + selected_view.apply_over( + cx, + live! { + architecture_tag = { visible: (is_architecture_visible), caption = { text: (architecture), draw_text: { color: (text_color) }}} + params_size_tag = { visible: (is_params_size_visible), caption = { text: (params_size), draw_text: { color: (text_color) }}} + }, + ); + } + } self.redraw(cx); } From 7f83e822e292bc325d6b8fbd4ffbeb151136b32c Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:15:10 -0300 Subject: [PATCH 17/51] remove unused model loader error wrapper --- src/data/chats/model_loader.rs | 56 ++++++---------------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index 62ced85f..fe386d9d 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -12,38 +12,6 @@ use std::{ thread, }; -/// Immutable handleable error type for the model loader. -/// -/// Actually, just a wrapper around `anyhow::Error` to support `Clone`. -#[derive(Debug, Clone)] -pub struct ModelLoaderError(Arc); - -impl std::error::Error for ModelLoaderError {} - -impl std::fmt::Display for ModelLoaderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ModelLoaderError: {}", self.0) - } -} - -impl From for ModelLoaderError { - fn from(err: anyhow::Error) -> Self { - Self(Arc::new(err)) - } -} - -impl From for ModelLoaderError { - fn from(err: String) -> Self { - Self(Arc::new(anyhow!(err))) - } -} - -impl From<&'static str> for ModelLoaderError { - fn from(err: &'static str) -> Self { - Self(Arc::new(anyhow!(err))) - } -} - /// All posible states in which the loader can be. #[derive(Debug, Default, Clone)] pub enum ModelLoaderStatus { @@ -51,7 +19,7 @@ pub enum ModelLoaderStatus { Unloaded, Loading, Loaded, - Failed(#[allow(dead_code)] ModelLoaderError), + Failed, } #[derive(Default)] @@ -73,10 +41,10 @@ impl ModelLoader { &mut self, file_id: FileID, command_sender: Sender, - ) -> Result<(), ModelLoaderError> { + ) -> Result<(), anyhow::Error> { match self.status() { ModelLoaderStatus::Loading => { - return Err(anyhow!("ModelLoader is already loading a model").into()); + return Err(anyhow!("ModelLoader is already loading a model")); } ModelLoaderStatus::Loaded => { if let Some(prev_file_id) = self.file_id() { @@ -100,21 +68,17 @@ impl ModelLoader { Ok(()) } Ok(response) => { - let msg = format!("Unexpected response: {:?}", response); - let err = ModelLoaderError::from(msg); - self.set_status(ModelLoaderStatus::Failed(err.clone())); - Err(err) + self.set_status(ModelLoaderStatus::Failed); + Err(anyhow!("Unexpected response: {:?}", response)) } Err(err) => { - let err = ModelLoaderError::from(err); - self.set_status(ModelLoaderStatus::Failed(err.clone())); - Err(err) + self.set_status(ModelLoaderStatus::Failed); + Err(anyhow!(err)) } } } else { - let err = ModelLoaderError::from("Internal communication error"); - self.set_status(ModelLoaderStatus::Failed(err.clone())); - Err(err) + self.set_status(ModelLoaderStatus::Failed); + Err(anyhow!("Internal communication error")) }; SignalToUI::set_ui_signal(); @@ -155,7 +119,7 @@ impl ModelLoader { } pub fn is_failed(&self) -> bool { - matches!(self.status(), ModelLoaderStatus::Failed(_)) + matches!(self.status(), ModelLoaderStatus::Failed) } pub fn is_finished(&self) -> bool { From b7c0a0855f42a026c4e51a0f8886d43d6570a066 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:18:29 -0300 Subject: [PATCH 18/51] rename model loader load funcs --- src/data/chats/chat.rs | 4 ++-- src/data/chats/mod.rs | 2 +- src/data/chats/model_loader.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data/chats/chat.rs b/src/data/chats/chat.rs index 6adf5edf..1b07db6d 100644 --- a/src/data/chats/chat.rs +++ b/src/data/chats/chat.rs @@ -245,7 +245,7 @@ impl Chat { content: "You are a helpful, respectful, and honest assistant.".to_string(), role: Role::System, name: None, - } + }, ); } @@ -298,7 +298,7 @@ impl Chat { let wanted_file = wanted_file.clone(); let command_sender = backend.command_sender.clone(); thread::spawn(move || { - if let Err(err) = model_loader.load_blocking(wanted_file.id, command_sender.clone()) { + if let Err(err) = model_loader.load(wanted_file.id, command_sender.clone()) { eprintln!("Error loading model: {}", err); return; } diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 086630e9..a07fff44 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -68,7 +68,7 @@ impl Chats { chat.save(); } self.model_loader - .load(file.id, self.backend.command_sender.clone()); + .load_async(file.id, self.backend.command_sender.clone()); } pub fn get_current_chat_id(&self) -> Option { diff --git a/src/data/chats/model_loader.rs b/src/data/chats/model_loader.rs index fe386d9d..00667053 100644 --- a/src/data/chats/model_loader.rs +++ b/src/data/chats/model_loader.rs @@ -37,7 +37,7 @@ impl ModelLoader { Self::default() } - pub fn load_blocking( + pub fn load( &mut self, file_id: FileID, command_sender: Sender, @@ -85,10 +85,10 @@ impl ModelLoader { result } - pub fn load(&mut self, file_id: FileID, command_sender: Sender) { + pub fn load_async(&mut self, file_id: FileID, command_sender: Sender) { let mut self_clone = self.clone(); thread::spawn(move || { - if let Err(err) = self_clone.load_blocking(file_id, command_sender) { + if let Err(err) = self_clone.load(file_id, command_sender) { eprintln!("Error loading model: {}", err); } }); From a67e23b7509a889c3081406f0e6e19db9d767787 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:24:48 -0300 Subject: [PATCH 19/51] revert some borrowing changes --- src/chat/chat_panel.rs | 2 +- src/data/chats/mod.rs | 8 ++++---- src/data/store.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index d23a804d..c446f5b0 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -530,7 +530,7 @@ impl WidgetMatchEvent for ChatPanel { store .chats - .create_empty_chat_and_load_file(downloaded_file.file); + .create_empty_chat_and_load_file(&downloaded_file.file); } _ => {} } diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index a07fff44..87c66a71 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -57,7 +57,7 @@ impl Chats { .map(|c| c.borrow().id) } - pub fn load_model(&mut self, file: File) { + pub fn load_model(&mut self, file: &File) { self.cancel_chat_streaming(); if self.model_loader.is_loading() { @@ -68,7 +68,7 @@ impl Chats { chat.save(); } self.model_loader - .load_async(file.id, self.backend.command_sender.clone()); + .load_async(file.id.clone(), self.backend.command_sender.clone()); } pub fn get_current_chat_id(&self) -> Option { @@ -137,7 +137,7 @@ impl Chats { self.saved_chats.push(new_chat); } - pub fn create_empty_chat_and_load_file(&mut self, file: File) { + pub fn create_empty_chat_and_load_file(&mut self, file: &File) { let new_chat = RefCell::new(Chat::new(self.chats_dir.clone())); new_chat.borrow().save(); @@ -151,7 +151,7 @@ impl Chats { .as_ref() .map_or(true, |m| *m.id != file.id) { - let _ = self.load_model(file); + let _ = self.load_model(&file); } } diff --git a/src/data/store.rs b/src/data/store.rs index 2d0232a4..37e7ab94 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -87,7 +87,7 @@ impl Store { store } - pub fn load_model(&mut self, file: File) { + pub fn load_model(&mut self, file: &File) { self.chats.load_model(file); } @@ -309,7 +309,7 @@ impl Store { .find(|d| d.file.id == *file_id) .map(|d| d.file.clone()) { - self.load_model(file); + self.load_model(&file); } } } From 7dc89716d43189487fdebb6309863c20a22fd91f Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:48:57 -0300 Subject: [PATCH 20/51] extract `hex_rgb_color` fn to utils --- src/chat/model_selector.rs | 17 ++++------------- src/shared/utils.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index b36d6201..c6228818 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -1,6 +1,9 @@ use crate::{ data::store::Store, - shared::{actions::ChatAction, utils::format_model_size}, + shared::{ + actions::ChatAction, + utils::{format_model_size, hex_rgb_color}, + }, }; use makepad_widgets::*; @@ -458,15 +461,3 @@ fn options_to_display(store: &Store) -> bool { fn no_active_model(store: &Store) -> bool { store.get_loaded_downloaded_file().is_none() && store.get_loading_file().is_none() } - -fn rgb_color(r: u8, g: u8, b: u8) -> Vec4 { - vec4(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) -} - -fn hex_rgb_color(hex: u32) -> Vec4 { - let r = ((hex >> 16) & 0xFF) as u8; - let g = ((hex >> 8) & 0xFF) as u8; - let b = (hex & 0xFF) as u8; - - rgb_color(r, g, b) -} diff --git a/src/shared/utils.rs b/src/shared/utils.rs index cdb245cf..d2bd920e 100644 --- a/src/shared/utils.rs +++ b/src/shared/utils.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use makepad_widgets::math_f32::{vec4, Vec4}; pub const BYTES_PER_MB: f64 = 1_048_576.0; // (1024^2) pub const HUGGING_FACE_BASE_URL: &str = "https://huggingface.co"; @@ -26,3 +27,12 @@ pub fn format_model_downloaded_size(size: &str, progress: f64) -> Result pub fn hugging_face_model_url(model_id: &str) -> String { format!("{}/{}", HUGGING_FACE_BASE_URL, model_id) } + +/// Convert from hex color notation to makepad's Vec4 color. +/// Ex: Converts `0xff33cc` into `vec4(1.0, 0.2, 0.8, 1.0)`. +pub fn hex_rgb_color(hex: u32) -> Vec4 { + let r = ((hex >> 16) & 0xFF) as f32 / 255.0; + let g = ((hex >> 8) & 0xFF) as f32 / 255.0; + let b = (hex & 0xFF) as f32 / 255.0; + vec4(r, g, b, 1.0) +} From 2d0dd6e920c4ef715e05b63aad49e428b0adb111 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:28:10 -0300 Subject: [PATCH 21/51] fix - default to loaded model when no model in chat --- src/data/store.rs | 50 +++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/data/store.rs b/src/data/store.rs index 37e7ab94..87f0dcb4 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -114,16 +114,20 @@ impl Store { pub fn send_chat_message(&mut self, prompt: String) { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { - if let Some(file_id) = &chat.last_used_file_id { - if let Some(file) = self.downloads.get_file(file_id) { - chat.send_message_to_model( - prompt, - file, - self.chats.model_loader.clone(), - &self.backend, - ); - chat.save(); - } + let wanted_file = chat + .last_used_file_id + .as_ref() + .and_then(|file_id| self.downloads.get_file(file_id)) + .or_else(|| self.chats.loaded_model.as_ref()); + + if let Some(file) = wanted_file { + chat.send_message_to_model( + prompt, + file, + self.chats.model_loader.clone(), + &self.backend, + ); + chat.save(); } } } @@ -139,17 +143,21 @@ impl Store { // used after `edit_chat_message` and keep concerns separated. pub fn edit_chat_message_regenerating(&mut self, message_id: usize, updated_message: String) { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { - if let Some(file_id) = &chat.last_used_file_id { - if let Some(file) = self.downloads.get_file(file_id) { - chat.remove_messages_from(message_id); - chat.send_message_to_model( - updated_message, - file, - self.chats.model_loader.clone(), - &self.backend, - ); - chat.save(); - } + let wanted_file = chat + .last_used_file_id + .as_ref() + .and_then(|file_id| self.downloads.get_file(file_id)) + .or_else(|| self.chats.loaded_model.as_ref()); + + if let Some(file) = wanted_file { + chat.remove_messages_from(message_id); + chat.send_message_to_model( + updated_message, + file, + self.chats.model_loader.clone(), + &self.backend, + ); + chat.save(); } } } From c6f59f6d92e76e1da01f276f71dbb893deded5a7 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:47:35 -0300 Subject: [PATCH 22/51] extract fallback logic of the previous fix --- src/data/chats/mod.rs | 15 +++++++++++++++ src/data/store.rs | 20 ++++++++++---------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 87c66a71..ef95d75e 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -128,6 +128,21 @@ impl Chats { Ok(()) } + /// Get the file id to use with this chat, or the loaded file id as a fallback. + /// If the fallback is used, the chat is updated with this, and persisted. + pub fn get_or_init_chat_file_id(&self, chat_id: ChatID) -> Option { + let mut chat = self.get_chat_by_id(chat_id)?.borrow_mut(); + + if let Some(file_id) = chat.last_used_file_id.clone() { + Some(file_id) + } else { + let file_id = self.loaded_model.as_ref().map(|m| m.id.clone())?; + chat.last_used_file_id = Some(file_id.clone()); + chat.save(); + Some(file_id) + } + } + pub fn create_empty_chat(&mut self) { let new_chat = RefCell::new(Chat::new(self.chats_dir.clone())); diff --git a/src/data/store.rs b/src/data/store.rs index 87f0dcb4..86da07f7 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -114,11 +114,11 @@ impl Store { pub fn send_chat_message(&mut self, prompt: String) { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { - let wanted_file = chat - .last_used_file_id - .as_ref() - .and_then(|file_id| self.downloads.get_file(file_id)) - .or_else(|| self.chats.loaded_model.as_ref()); + let wanted_file = self + .chats + .get_or_init_chat_file_id(chat.id) + .map(|file_id| self.downloads.get_file(&file_id)) + .flatten(); if let Some(file) = wanted_file { chat.send_message_to_model( @@ -143,11 +143,11 @@ impl Store { // used after `edit_chat_message` and keep concerns separated. pub fn edit_chat_message_regenerating(&mut self, message_id: usize, updated_message: String) { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { - let wanted_file = chat - .last_used_file_id - .as_ref() - .and_then(|file_id| self.downloads.get_file(file_id)) - .or_else(|| self.chats.loaded_model.as_ref()); + let wanted_file = self + .chats + .get_or_init_chat_file_id(chat.id) + .map(|file_id| self.downloads.get_file(&file_id)) + .flatten(); if let Some(file) = wanted_file { chat.remove_messages_from(message_id); From 483c3487b877ec6203a8d42186a29e9d26d86d4c Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:50:58 -0300 Subject: [PATCH 23/51] restore load model on selector change --- src/chat/chat_panel.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index c446f5b0..7d921855 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -511,6 +511,8 @@ impl WidgetMatchEvent for ChatPanel { } if let ModelSelectorAction::Selected(downloaded_file) = action.cast() { + store.load_model(&downloaded_file.file); + if let Some(chat) = store.chats.get_current_chat() { chat.borrow_mut().last_used_file_id = Some(downloaded_file.file.id.clone()); chat.borrow().save(); From f1865ea7c7d8cea3b32ceb11a02837c6b85a54b1 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:20:55 -0300 Subject: [PATCH 24/51] fix - refcell borrow crash when referenced file deleted --- src/data/chats/mod.rs | 7 ++++--- src/data/store.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index ef95d75e..fa6f540b 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -129,10 +129,11 @@ impl Chats { } /// Get the file id to use with this chat, or the loaded file id as a fallback. + /// The fallback is used if the chat does not have a file id set, or, if it has + /// one but references a no longer existing (deleted) file. + /// /// If the fallback is used, the chat is updated with this, and persisted. - pub fn get_or_init_chat_file_id(&self, chat_id: ChatID) -> Option { - let mut chat = self.get_chat_by_id(chat_id)?.borrow_mut(); - + pub fn get_or_init_chat_file_id(&self, chat: &mut Chat) -> Option { if let Some(file_id) = chat.last_used_file_id.clone() { Some(file_id) } else { diff --git a/src/data/store.rs b/src/data/store.rs index 86da07f7..a8f15648 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -116,7 +116,7 @@ impl Store { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { let wanted_file = self .chats - .get_or_init_chat_file_id(chat.id) + .get_or_init_chat_file_id(&mut chat) .map(|file_id| self.downloads.get_file(&file_id)) .flatten(); @@ -145,7 +145,7 @@ impl Store { if let Some(mut chat) = self.chats.get_current_chat().map(|c| c.borrow_mut()) { let wanted_file = self .chats - .get_or_init_chat_file_id(chat.id) + .get_or_init_chat_file_id(&mut chat) .map(|file_id| self.downloads.get_file(&file_id)) .flatten(); From 7a69f74bb866c13f93a2092b8e91a55ce4ada10e Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:19:14 -0300 Subject: [PATCH 25/51] remove "resume chat" visuals --- resources/icons/play_arrow.svg | 4 ---- src/landing/model_files_item.rs | 24 ++------------------- src/my_models/downloaded_files_row.rs | 31 +++------------------------ 3 files changed, 5 insertions(+), 54 deletions(-) delete mode 100644 resources/icons/play_arrow.svg diff --git a/resources/icons/play_arrow.svg b/resources/icons/play_arrow.svg deleted file mode 100644 index 2b289cf5..00000000 --- a/resources/icons/play_arrow.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/landing/model_files_item.rs b/src/landing/model_files_item.rs index f9d9404e..127691f0 100644 --- a/src/landing/model_files_item.rs +++ b/src/landing/model_files_item.rs @@ -21,7 +21,6 @@ live_design! { ICON_DOWNLOAD = dep("crate://self/resources/icons/download.svg") START_CHAT = dep("crate://self/resources/icons/start_chat.svg") - RESUME_CHAT = dep("crate://self/resources/icons/play_arrow.svg") ICON_PAUSE = dep("crate://self/resources/icons/pause_download.svg") ICON_CANCEL = dep("crate://self/resources/icons/cancel_download.svg") @@ -69,17 +68,6 @@ live_design! { } } - ResumeChatButton = { - draw_bg: { color: #099250, border_color: #09925033 } - text: "Resume Chat" - draw_text: { - color: #fff; - } - draw_icon: { - svg_file: (RESUME_CHAT), - } - } - DownloadPendingButton = { width: 25, height: 25, @@ -202,7 +190,6 @@ live_design! { cell4 = { download_button = { visible: false } start_chat_button = { visible: false } - resume_chat_button = { visible: false } download_pending_controls = { visible: false } } } @@ -258,7 +245,8 @@ impl Widget for ModelFilesItem { matches!(download.status, PendingDownloadsStatus::Downloading); let is_retry_download_visible = matches!(download.status, PendingDownloadsStatus::Error); - let is_cancel_download_visible = !matches!(download.status, PendingDownloadsStatus::Initializing); + let is_cancel_download_visible = + !matches!(download.status, PendingDownloadsStatus::Initializing); let status_color = match download.status { PendingDownloadsStatus::Downloading | PendingDownloadsStatus::Initializing => { @@ -303,7 +291,6 @@ impl Widget for ModelFilesItem { } } start_chat_button = { visible: false } - resume_chat_button = { visible: false } download_button = { visible: false } }}, ); @@ -314,7 +301,6 @@ impl Widget for ModelFilesItem { live! { cell4 = { download_pending_controls = { visible: false } start_chat_button = { visible: false } - resume_chat_button = { visible: true } download_button = { visible: false } }}, ); @@ -324,7 +310,6 @@ impl Widget for ModelFilesItem { live! { cell4 = { download_pending_controls = { visible: false } start_chat_button = { visible: true } - resume_chat_button = { visible: false } download_button = { visible: false } }}, ); @@ -335,7 +320,6 @@ impl Widget for ModelFilesItem { live! { cell4 = { download_pending_controls = { visible: false } start_chat_button = { visible: false } - resume_chat_button = { visible: false } download_button = { visible: true } }}, ); @@ -364,10 +348,6 @@ impl WidgetMatchEvent for ModelFilesItem { cx.widget_action(widget_uid, &scope.path, ChatAction::Start(file_id.clone())); } - if self.button(id!(resume_chat_button)).clicked(&actions) { - cx.widget_action(widget_uid, &scope.path, ChatAction::Resume); - } - if [id!(resume_download_button), id!(retry_download_button)] .iter() .any(|id| self.button(*id).clicked(&actions)) diff --git a/src/my_models/downloaded_files_row.rs b/src/my_models/downloaded_files_row.rs index 3b02661d..7072a707 100644 --- a/src/my_models/downloaded_files_row.rs +++ b/src/my_models/downloaded_files_row.rs @@ -1,8 +1,8 @@ +use crate::shared::actions::ChatAction; +use crate::shared::modal::ModalWidgetExt; +use crate::shared::utils::format_model_size; use makepad_widgets::*; use moxin_protocol::data::{DownloadedFile, FileID}; -use crate::shared::utils::format_model_size; -use crate::shared::modal::ModalWidgetExt; -use crate::shared::actions::ChatAction; live_design! { import makepad_widgets::base::*; @@ -111,23 +111,6 @@ live_design! { } } - resume_chat_button = { - width: 140 - visible: false - draw_bg: { - color: (MODEL_CTA_COLOR) - } - text: "Resume Chat", - draw_text: { - color: #fff - text_style: {font_size: 9} - } - draw_icon: { - svg_file: (ICON_PLAY) - color: #fff - } - } - { width: Fill, height: Fit } info_button = { @@ -265,8 +248,6 @@ impl Widget for DownloadedFilesRow { self.button(id!(start_chat_button)) .set_visible(!props.show_resume); - self.button(id!(resume_chat_button)) - .set_visible(props.show_resume); self.view.draw_walk(cx, scope, walk) } @@ -282,12 +263,6 @@ impl WidgetMatchEvent for DownloadedFilesRow { } } - if self.button(id!(resume_chat_button)).clicked(actions) { - if let Some(_) = &self.file_id { - cx.widget_action(widget_uid, &scope.path, ChatAction::Resume); - } - } - if self.button(id!(row_actions.info_button)).clicked(actions) { self.modal(id!(info_modal)).open_modal(cx); } From a0d50bdb31e34d8a7c3377bb15e1157a85074278 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:39:00 -0300 Subject: [PATCH 26/51] show always "chat with model" in downloads table --- src/my_models/downloaded_files_row.rs | 4 ---- src/my_models/downloaded_files_table.rs | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/src/my_models/downloaded_files_row.rs b/src/my_models/downloaded_files_row.rs index 7072a707..1f3e1b98 100644 --- a/src/my_models/downloaded_files_row.rs +++ b/src/my_models/downloaded_files_row.rs @@ -183,7 +183,6 @@ live_design! { pub struct DownloadedFilesRowProps { pub downloaded_file: DownloadedFile, - pub show_resume: bool, } #[derive(Live, LiveHook, Widget)] @@ -246,9 +245,6 @@ impl Widget for DownloadedFilesRow { self.label(id!(h_wrapper.date_added_tag.date_added)) .set_text(&formatted_date); - self.button(id!(start_chat_button)) - .set_visible(!props.show_resume); - self.view.draw_walk(cx, scope, walk) } } diff --git a/src/my_models/downloaded_files_table.rs b/src/my_models/downloaded_files_table.rs index 94ea05ab..74939ad6 100644 --- a/src/my_models/downloaded_files_table.rs +++ b/src/my_models/downloaded_files_table.rs @@ -114,14 +114,6 @@ impl Widget for DownloadedFilesTable { let entries_count = self.current_results.len(); let last_item_id = if entries_count > 0 { entries_count } else { 0 }; - let loaded_model_id = if let Some(loaded_model) = - &scope.data.get::().unwrap().chats.loaded_model - { - Some(loaded_model.id.clone()) - } else { - None - }; - while let Some(item) = self.view.draw_walk(cx, scope, walk).step() { if let Some(mut list) = item.as_portal_list().borrow_mut() { list.set_item_range(cx, 0, last_item_id + 1); @@ -144,12 +136,8 @@ impl Widget for DownloadedFilesTable { item.as_downloaded_files_row() .set_file_id(file_data.file.id.clone()); - let is_model_file_loaded = - loaded_model_id.clone().map_or(false, |f| *f == file_data.file.id); - let props = DownloadedFilesRowProps { downloaded_file: file_data.clone(), - show_resume: is_model_file_loaded, }; let mut scope = Scope::with_props(&props); item.draw_all(cx, &mut scope); From ab9016341ad953550f16a10601aaca8224a3ee3f Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:57:31 -0300 Subject: [PATCH 27/51] show always "chat with model" in discover --- src/landing/model_files_item.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/landing/model_files_item.rs b/src/landing/model_files_item.rs index 127691f0..15c6ce4b 100644 --- a/src/landing/model_files_item.rs +++ b/src/landing/model_files_item.rs @@ -295,25 +295,14 @@ impl Widget for ModelFilesItem { }}, ); } else if files_info.file.downloaded { - if files_info.is_current_chat { - self.apply_over( - cx, - live! { cell4 = { - download_pending_controls = { visible: false } - start_chat_button = { visible: false } - download_button = { visible: false } - }}, - ); - } else { - self.apply_over( - cx, - live! { cell4 = { - download_pending_controls = { visible: false } - start_chat_button = { visible: true } - download_button = { visible: false } - }}, - ); - } + self.apply_over( + cx, + live! { cell4 = { + download_pending_controls = { visible: false } + start_chat_button = { visible: true } + download_button = { visible: false } + }}, + ); } else { self.apply_over( cx, From b5bfeddf3936c2fcbe7027ff9f957f4998f3d0e4 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:14:14 -0300 Subject: [PATCH 28/51] set last used model when clicking "chat with model" --- src/chat/chat_panel.rs | 14 +++----------- src/data/chats/mod.rs | 20 +++++++------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index 7d921855..f7fcce60 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -522,17 +522,9 @@ impl WidgetMatchEvent for ChatPanel { match action.cast() { ChatAction::Start(file_id) => { - let downloaded_file = store - .downloads - .downloaded_files - .iter() - .find(|file| file.file.id == file_id) - .expect("Attempted to start chat with a no longer existing file") - .clone(); - - store - .chats - .create_empty_chat_and_load_file(&downloaded_file.file); + if let Some(file) = store.downloads.get_file(&file_id) { + store.chats.create_empty_chat_and_load_file(file); + } } _ => {} } diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index fa6f540b..43ff1471 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -63,6 +63,7 @@ impl Chats { if self.model_loader.is_loading() { return; } + if let Some(mut chat) = self.get_current_chat().map(|c| c.borrow_mut()) { chat.last_used_file_id = Some(file.id.clone()); chat.save(); @@ -154,21 +155,14 @@ impl Chats { } pub fn create_empty_chat_and_load_file(&mut self, file: &File) { - let new_chat = RefCell::new(Chat::new(self.chats_dir.clone())); - new_chat.borrow().save(); + let mut new_chat = Chat::new(self.chats_dir.clone()); + new_chat.last_used_file_id = Some(file.id.clone()); + new_chat.save(); - self.cancel_chat_streaming(); + self.current_chat_id = Some(new_chat.id); + self.saved_chats.push(RefCell::new(new_chat)); - self.current_chat_id = Some(new_chat.borrow().id); - self.saved_chats.push(new_chat); - - if self - .loaded_model - .as_ref() - .map_or(true, |m| *m.id != file.id) - { - let _ = self.load_model(&file); - } + self.load_model(file); } pub fn remove_chat(&mut self, chat_id: ChatID) { From 323018ff26a017d34645b4b6bcf1538a18645376 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:35:02 -0300 Subject: [PATCH 29/51] optimized always set last used model when load is called --- src/data/chats/mod.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/data/chats/mod.rs b/src/data/chats/mod.rs index 43ff1471..26efe676 100644 --- a/src/data/chats/mod.rs +++ b/src/data/chats/mod.rs @@ -60,14 +60,19 @@ impl Chats { pub fn load_model(&mut self, file: &File) { self.cancel_chat_streaming(); + if let Some(mut chat) = self.get_current_chat().map(|c| c.borrow_mut()) { + let new_file_id = Some(file.id.clone()); + + if chat.last_used_file_id != new_file_id { + chat.last_used_file_id = new_file_id; + chat.save(); + } + } + if self.model_loader.is_loading() { return; } - if let Some(mut chat) = self.get_current_chat().map(|c| c.borrow_mut()) { - chat.last_used_file_id = Some(file.id.clone()); - chat.save(); - } self.model_loader .load_async(file.id.clone(), self.backend.command_sender.clone()); } From e2a27afc15d8ae357a43c929851fd6b94e222750 Mon Sep 17 00:00:00 2001 From: noxware <7684329+noxware@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:00:07 -0300 Subject: [PATCH 30/51] remove leftover reference to play_arrow.svg --- src/my_models/downloaded_files_row.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/my_models/downloaded_files_row.rs b/src/my_models/downloaded_files_row.rs index 1f3e1b98..4267b9c4 100644 --- a/src/my_models/downloaded_files_row.rs +++ b/src/my_models/downloaded_files_row.rs @@ -15,7 +15,6 @@ live_design! { import crate::my_models::delete_model_modal::DeleteModelModal; ICON_START_CHAT = dep("crate://self/resources/icons/start_chat.svg") - ICON_PLAY = dep("crate://self/resources/icons/play_arrow.svg") ICON_INFO = dep("crate://self/resources/icons/info.svg") ICON_DELETE = dep("crate://self/resources/icons/delete.svg") MODEL_CTA_COLOR = #127487 From f86ad1a12e8b53e1a79fa2c941f0868601f4091d Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 15 Aug 2024 13:20:15 -0700 Subject: [PATCH 31/51] moxin-runner: explicitly detect CUDA on Linux The WasmEdge `install_v2.sh` script currently treats the `--noavx` option as a hard override that bypasses CUDA detection. Thus, we must manually check for CUDA first, and if CUDA is detected, we must *not* pass in the `--noavx` option. We now only pass in the `--noavx` option if CUDA is not found *and* if the CPU doesn't support AVX512. It is not clear what WasmEdge will do if CUDA is detected but the runtime determines that the graphics card is insufficient for its needs -- will it revert back to CPU instructions that require AVX512?? --- moxin-runner/src/main.rs | 52 ++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/moxin-runner/src/main.rs b/moxin-runner/src/main.rs index b93652fd..5221e972 100644 --- a/moxin-runner/src/main.rs +++ b/moxin-runner/src/main.rs @@ -316,10 +316,13 @@ fn install_wasmedge>(install_path: P) -> Result Found CUDA installation: {cuda:?}"); + + // If the current machine doesn't have CUDA and the CPU doesn't support AVX512, + // tell the install script to select the no-AVX WASI-nn plugin version. #[cfg(target_arch = "x86_64")] - if !is_x86_feature_detected!("avx512f") { + if cuda.is_none() && !is_x86_feature_detected!("avx512f") { bash_cmd.arg("--noavx"); } @@ -493,6 +496,7 @@ fn wasmedge_root_dir_from_env_vars() -> Option { } /// Versions of CUDA that WasmEdge supports. +#[derive(Debug)] enum CudaVersion { /// CUDA Version 12 V12, @@ -505,26 +509,32 @@ enum CudaVersion { /// This function first runs `nvcc --version` on both Linux and Windows, /// and if that fails, it will try `/usr/local/cuda/bin/nvcc --version` on Linux only. fn get_cuda_version() -> Option { - let mut output = Command::new("nvcc") - .arg("--version") - .output(); - - #[cfg(target_os = "linux")] { - output = output.or_else(|_| - Command::new("/usr/local/cuda/bin/nvcc") - .arg("--version") - .output() - ); + #[cfg(target_os = "macos")] { + None } - let output = output.ok()?; - let output = String::from_utf8_lossy(&output.stdout); - if output.contains("V12") { - Some(CudaVersion::V12) - } else if output.contains("V11") { - Some(CudaVersion::V11) - } else { - None + #[cfg(not(target_os = "macos"))] { + let mut output = Command::new("nvcc") + .arg("--version") + .output(); + + #[cfg(target_os = "linux")] { + output = output.or_else(|_| + Command::new("/usr/local/cuda/bin/nvcc") + .arg("--version") + .output() + ); + } + + let output = output.ok()?; + let output = String::from_utf8_lossy(&output.stdout); + if output.contains("V12") { + Some(CudaVersion::V12) + } else if output.contains("V11") { + Some(CudaVersion::V11) + } else { + None + } } } From 391bc4792d4c1d8e008d661bfe21780190dc47cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Aug 2024 18:31:42 +0000 Subject: [PATCH 32/51] Bump openssl from 0.10.64 to 0.10.66 Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.64 to 0.10.66. - [Release notes](https://github.com/sfackler/rust-openssl/releases) - [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.64...openssl-v0.10.66) --- updated-dependencies: - dependency-name: openssl dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b10f9c8f..f6c9b457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1751,9 +1751,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -1783,9 +1783,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", From 47e8895b986c478c039954d5c5d061eaf15be476 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Thu, 8 Aug 2024 11:08:54 -0300 Subject: [PATCH 33/51] Reimplement Tooltip widget --- src/app.rs | 56 ++++++++--------- src/chat/chat_params.rs | 20 +++--- src/shared/actions.rs | 9 +-- src/shared/modal.rs | 31 ++++++--- src/shared/tooltip.rs | 135 ++++++++++++++++++++++++++++++++-------- 5 files changed, 170 insertions(+), 81 deletions(-) diff --git a/src/app.rs b/src/app.rs index c01e4812..06bac6e7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,12 +5,11 @@ use crate::chat::chat_panel::ChatPanelAction; use crate::data::downloads::DownloadPendingNotification; use crate::data::store::*; use crate::landing::model_files_item::ModelFileItemAction; -use crate::shared::actions::{ChatAction, DownloadAction, TooltipAction}; +use crate::shared::actions::{ChatAction, DownloadAction}; use crate::shared::download_notification_popup::{ DownloadNotificationPopupWidgetRefExt, DownloadResult, PopupAction, }; -use crate::shared::portal::{PortalAction, PortalViewWidgetRefExt, PortalWidgetRefExt}; -use crate::shared::tooltip::TooltipWidgetRefExt; +use crate::shared::portal::{PortalAction, PortalWidgetRefExt}; use makepad_widgets::*; live_design! { @@ -23,7 +22,6 @@ live_design! { import crate::shared::modal::*; import crate::shared::widgets::SidebarMenuButton; import crate::shared::download_notification_popup::DownloadNotificationPopup; - import crate::shared::tooltip::Tooltip; import crate::shared::desktop_buttons::MoxinDesktopButton; import crate::landing::landing_screen::LandingScreen; @@ -119,9 +117,9 @@ live_design! { popup_download_success = {} } - tooltip_portal_view = { - tooltip = {} - } + // tooltip_portal_view = { + // tooltip = {} + // } chat_history_card_options_portal_view = { chat_history_card_options = {} @@ -254,28 +252,28 @@ impl MatchEvent for App { chat_radio_button.select(cx, &mut Scope::empty()); } - match action.as_widget_action().cast() { - TooltipAction::Show(text, pos) => { - let mut tooltip = self.ui.tooltip(id!(tooltip)); - tooltip.set_text(&text); - - let tooltip_portal_view = self.ui.portal_view(id!(tooltip_portal_view)); - tooltip_portal_view.apply_over_and_redraw( - cx, - live! { - padding: { left: (pos.x), top: (pos.y) } - }, - ); - - let mut portal = self.ui.portal(id!(portal_root)); - let _ = portal.show_portal_view_by_id(cx, live_id!(tooltip_portal_view)); - } - TooltipAction::Hide => { - let mut portal = self.ui.portal(id!(portal_root)); - let _ = portal.close(cx); - } - _ => {} - } + // match action.as_widget_action().cast() { + // TooltipAction::Show(text, pos) => { + // let mut tooltip = self.ui.tooltip(id!(tooltip)); + // tooltip.set_text(&text); + + // let tooltip_portal_view = self.ui.portal_view(id!(tooltip_portal_view)); + // tooltip_portal_view.apply_over_and_redraw( + // cx, + // live! { + // padding: { left: (pos.x), top: (pos.y) } + // }, + // ); + + // let mut portal = self.ui.portal(id!(portal_root)); + // let _ = portal.show_portal_view_by_id(cx, live_id!(tooltip_portal_view)); + // } + // TooltipAction::Hide => { + // let mut portal = self.ui.portal(id!(portal_root)); + // let _ = portal.close(cx); + // } + // _ => {} + // } if let ChatHistoryCardOptionsAction::Selected(chat_id, cords) = action.as_widget_action().cast() diff --git a/src/chat/chat_params.rs b/src/chat/chat_params.rs index 560be57f..359e8b17 100644 --- a/src/chat/chat_params.rs +++ b/src/chat/chat_params.rs @@ -2,7 +2,7 @@ use makepad_widgets::*; use crate::{ data::{chats::chat::ChatID, store::Store}, - shared::actions::TooltipAction, + shared::tooltip::{TooltipAction, TooltipWidgetExt}, }; live_design! { @@ -11,6 +11,7 @@ live_design! { import crate::shared::styles::*; import crate::shared::widgets::*; + import crate::shared::tooltip::*; import makepad_draw::shader::std::*; ICON_CLOSE_PANEL = dep("crate://self/resources/icons/close_right_panel.svg") @@ -210,6 +211,8 @@ live_design! { } } } + + tooltip = {} } } @@ -416,12 +419,10 @@ impl ChatParams { ) { let widget_uid = self.widget_uid(); let slider = self.slider(slider_id); + if let Some(rect) = slider.label_hover_in(&actions) { - cx.widget_action( - widget_uid, - &scope.path, - TooltipAction::Show(text, rect.pos + offset), - ); + self.deref.tooltip(id!(tooltip)).set_text_and_pos(cx, &text, rect.pos + offset); + self.deref.tooltip(id!(tooltip)).show_tooltip(cx); } if slider.label_hover_out(&actions) { cx.widget_action(widget_uid, &scope.path, TooltipAction::Hide); @@ -440,11 +441,8 @@ impl ChatParams { let widget_uid = self.widget_uid(); let label = self.label(label_id); if let Some(rect) = label.hover_in(&actions) { - cx.widget_action( - widget_uid, - &scope.path, - TooltipAction::Show(text, rect.pos + offset), - ); + self.deref.tooltip(id!(tooltip)).set_text_and_pos(cx, &text, rect.pos + offset); + self.deref.tooltip(id!(tooltip)).show_tooltip(cx); } if label.hover_out(&actions) { cx.widget_action(widget_uid, &scope.path, TooltipAction::Hide); diff --git a/src/shared/actions.rs b/src/shared/actions.rs index 65202747..e3c1e36e 100644 --- a/src/shared/actions.rs +++ b/src/shared/actions.rs @@ -14,11 +14,4 @@ pub enum DownloadAction { Pause(FileID), Cancel(FileID), None, -} - -#[derive(Clone, DefaultNone, Debug)] -pub enum TooltipAction { - Show(String, DVec2), - Hide, - None, -} +} \ No newline at end of file diff --git a/src/shared/modal.rs b/src/shared/modal.rs index ccb7b06e..70c37e0c 100644 --- a/src/shared/modal.rs +++ b/src/shared/modal.rs @@ -10,8 +10,6 @@ live_design! { import crate::shared::portal::*; Modal = {{Modal}} { - opened: false - width: Fill height: Fill flow: Overlay @@ -44,8 +42,6 @@ live_design! { #[derive(Live, Widget)] pub struct Modal { - #[live] - opened: bool, #[live] #[find] content: View, @@ -62,6 +58,9 @@ pub struct Modal { layout: Layout, #[walk] walk: Walk, + + #[rust] + opened: bool, } impl LiveHook for Modal { @@ -131,12 +130,30 @@ impl WidgetMatchEvent for Modal { } } +impl Modal { + pub fn open_modal(&mut self, cx: &mut Cx) { + self.opened = true; + self.redraw(cx); + cx.sweep_lock(self.draw_bg.area()); + } + + pub fn close_modal(&mut self, cx: &mut Cx) { + self.opened = false; + self.draw_bg.redraw(cx); + cx.sweep_unlock(self.draw_bg.area()) + } +} + impl ModalRef { pub fn open_modal(&self, cx: &mut Cx) { if let Some(mut inner) = self.borrow_mut() { - inner.opened = true; - inner.redraw(cx); - cx.sweep_lock(inner.draw_bg.area()); + inner.open_modal(cx); + } + } + + pub fn close_modal(&self, cx: &mut Cx) { + if let Some(mut inner) = self.borrow_mut() { + inner.close_modal(cx); } } } diff --git a/src/shared/tooltip.rs b/src/shared/tooltip.rs index b724bdac..91674b5b 100644 --- a/src/shared/tooltip.rs +++ b/src/shared/tooltip.rs @@ -1,5 +1,7 @@ use makepad_widgets::*; +use super::modal::Modal; + live_design! { import makepad_widgets::base::*; import makepad_widgets::theme_desktop_dark::*; @@ -8,55 +10,136 @@ live_design! { import makepad_draw::shader::draw_color::DrawColor; import crate::shared::widgets::*; import crate::shared::styles::*; + import crate::shared::modal::*; - Tooltip = {{Tooltip}} { + Tooltip = {{Tooltip}}{ width: Fill, height: Fill, - flow: Down, - - { - width: Fit, - height: Fit, - - padding: 16, - - draw_bg: { - color: #fff, - border_width: 1.0, - border_color: #D0D5DD, - radius: 2. + + flow: Overlay + align: {x: 0.0, y: 0.0} + + draw_bg: { + fn pixel(self) -> vec4 { + return vec4(0., 0., 0., 0.0) } + } - label =