diff --git a/moxin-backend/src/backend_impls/chat_ui.rs b/moxin-backend/src/backend_impls/chat_ui.rs index 1cac73cb..411ca05d 100644 --- a/moxin-backend/src/backend_impls/chat_ui.rs +++ b/moxin-backend/src/backend_impls/chat_ui.rs @@ -316,22 +316,15 @@ fn create_wasi( file: &DownloadedFile, load_model: &LoadModelOptions, ) -> wasmedge_sdk::WasmEdgeResult { - let ctx_size = if load_model.n_ctx > 0 { - Some(load_model.n_ctx.to_string()) - } else { - None - }; + let ctx_size = Some(format!("{}", file.context_size)); let n_gpu_layers = match load_model.gpu_layers { moxin_protocol::protocol::GPULayers::Specific(n) => Some(n.to_string()), moxin_protocol::protocol::GPULayers::Max => None, }; - let batch_size = if load_model.n_batch > 0 { - Some(load_model.n_batch.to_string()) - } else { - None - }; + // Set n_batch to a fixed value of 128. + let batch_size = Some(format!("128")); let mut prompt_template = load_model.prompt_template.clone(); if prompt_template.is_none() && !file.prompt_template.is_empty() { diff --git a/moxin-backend/src/backend_impls/mod.rs b/moxin-backend/src/backend_impls/mod.rs index 0dd9d8c3..d91f0653 100644 --- a/moxin-backend/src/backend_impls/mod.rs +++ b/moxin-backend/src/backend_impls/mod.rs @@ -136,8 +136,6 @@ fn test_chat() { prompt_template: None, gpu_layers: moxin_protocol::protocol::GPULayers::Max, use_mlock: false, - n_batch: 512, - n_ctx: 512, rope_freq_scale: 0.0, rope_freq_base: 0.0, context_overflow_policy: moxin_protocol::protocol::ContextOverflowPolicy::StopAtLimit, @@ -211,8 +209,6 @@ fn test_chat_stop() { prompt_template: None, gpu_layers: moxin_protocol::protocol::GPULayers::Max, use_mlock: false, - n_batch: 512, - n_ctx: 512, rope_freq_scale: 0.0, rope_freq_base: 0.0, context_overflow_policy: moxin_protocol::protocol::ContextOverflowPolicy::StopAtLimit, diff --git a/moxin-protocol/src/protocol.rs b/moxin-protocol/src/protocol.rs index 9b250a58..694ef96c 100644 --- a/moxin-protocol/src/protocol.rs +++ b/moxin-protocol/src/protocol.rs @@ -28,8 +28,6 @@ pub struct LoadModelOptions { pub prompt_template: Option, pub gpu_layers: GPULayers, pub use_mlock: bool, - pub n_batch: u32, - pub n_ctx: u32, pub rope_freq_scale: f32, pub rope_freq_base: f32, diff --git a/src/app.rs b/src/app.rs index 47e00ced..82b1c061 100644 --- a/src/app.rs +++ b/src/app.rs @@ -335,7 +335,7 @@ impl MatchEvent for App { .ui .chat_history_card_options(id!(chat_history_card_options)); // TODO: Would be cool to listen for this action inside of the widget itself. - chat_history_card_options.selected(cx, chat_id, cords); + let _ = chat_history_card_options.selected(cx, chat_id, cords); } } } diff --git a/src/chat/chat_history_card.rs b/src/chat/chat_history_card.rs index 1fdfdf39..f63ec5a5 100644 --- a/src/chat/chat_history_card.rs +++ b/src/chat/chat_history_card.rs @@ -355,7 +355,7 @@ impl ChatHistoryCard { &mut self, cx: &mut Cx, actions: &Actions, - scope: &mut Scope, + _scope: &mut Scope, ) { for action in actions { if let ChatHistoryCardAction::ActivateTitleEdition(chat_id) = diff --git a/src/chat/chat_panel.rs b/src/chat/chat_panel.rs index 37c62fe1..6ec0a8fa 100644 --- a/src/chat/chat_panel.rs +++ b/src/chat/chat_panel.rs @@ -264,6 +264,7 @@ live_design! { padding: {left: 25, right: 25, bottom: 20}, no_downloaded_model = { + visible: false, width: Fill, height: Fill, @@ -317,6 +318,7 @@ live_design! { } no_model = { + visible: false, width: Fill, height: Fill, @@ -424,13 +426,25 @@ enum State { Unknown, NoModelsAvailable, NoModelSelected, - ModelSelectedWithEmptyChat, + ModelSelectedWithEmptyChat { + is_loading: bool, + }, ModelSelectedWithChat { + is_loading: bool, sticked_to_bottom: bool, is_streaming: bool, }, } +enum PromptInputMode { + Enabled, + Disabled, +} +enum PromptInputButton { + Send, + Stop, +} + #[derive(Live, LiveHook, Widget)] pub struct ChatPanel { #[deref] @@ -454,6 +468,7 @@ impl Widget for ChatPanel { State::ModelSelectedWithChat { is_streaming: true, sticked_to_bottom, + .. } => { if sticked_to_bottom { self.scroll_messages_to_bottom(cx); @@ -551,7 +566,7 @@ impl WidgetMatchEvent for ChatPanel { is_streaming: false, .. } - | State::ModelSelectedWithEmptyChat => { + | State::ModelSelectedWithEmptyChat { .. } => { self.handle_prompt_input_actions(cx, actions, scope); } State::ModelSelectedWithChat { @@ -579,142 +594,119 @@ impl WidgetMatchEvent for ChatPanel { impl ChatPanel { fn update_state(&mut self, scope: &mut Scope) { let store = scope.data.get_mut::().unwrap(); + //let loader = &store.chats.model_loader; self.state = if store.downloads.downloaded_files.is_empty() { State::NoModelsAvailable } else if store.chats.loaded_model.is_none() { State::NoModelSelected } else { - store - .chats - .get_current_chat() - .map_or(State::ModelSelectedWithEmptyChat, |chat| { + let is_loading = store.chats.get_currently_loading_model().is_some(); + + store.chats.get_current_chat().map_or( + State::ModelSelectedWithEmptyChat { is_loading }, + |chat| { if chat.borrow().messages.is_empty() { - State::ModelSelectedWithEmptyChat + State::ModelSelectedWithEmptyChat { is_loading } } else { State::ModelSelectedWithChat { + is_loading, sticked_to_bottom: self.portal_list_end_reached || !matches!(self.state, State::ModelSelectedWithChat { .. }), is_streaming: chat.borrow().is_streaming, } } - }) + }, + ) }; } fn update_prompt_input(&mut self, cx: &mut Cx) { match self.state { - State::ModelSelectedWithEmptyChat + State::ModelSelectedWithEmptyChat { is_loading: true } | State::ModelSelectedWithChat { + is_loading: true, .. + } => { + self.activate_prompt_input(cx, PromptInputMode::Disabled, PromptInputButton::Send); + } + State::ModelSelectedWithEmptyChat { is_loading: false } + | State::ModelSelectedWithChat { + is_loading: false, is_streaming: false, .. } => { - self.enable_or_disable_prompt_buttons(cx); - self.show_prompt_send_button(cx); + self.activate_prompt_input(cx, PromptInputMode::Enabled, PromptInputButton::Send); } State::ModelSelectedWithChat { is_streaming: true, .. } => { - let prompt_input = self.text_input(id!(main_prompt_input.prompt)); - prompt_input.apply_over( - cx, - live! { - draw_text: { prompt_enabled: 0.0 } - }, - ); - self.show_prompt_input_stop_button(cx); + self.activate_prompt_input(cx, PromptInputMode::Disabled, PromptInputButton::Stop); + } + _ => { + // Input prompts should not be visible in other conditions } - _ => {} } } - fn enable_or_disable_prompt_buttons(&mut self, cx: &mut Cx) { + fn activate_prompt_input( + &mut self, + cx: &mut Cx, + mode: PromptInputMode, + button: PromptInputButton, + ) { let prompt_input = self.text_input(id!(main_prompt_input.prompt)); - let enable = if !prompt_input.text().is_empty() { - 1.0 - } else { - 0.0 - }; - prompt_input.apply_over( - cx, - live! { - draw_text: { prompt_enabled: (enable) } - }, - ); - } - - fn show_prompt_send_button(&mut self, cx: &mut Cx) { - self.button(id!(main_prompt_input.prompt_send_button)) - .set_visible(true); - self.button(id!(main_prompt_input.prompt_stop_button)) - .set_visible(false); + let enabled = match mode { + PromptInputMode::Enabled => !prompt_input.text().is_empty(), + PromptInputMode::Disabled => false, + }; - let prompt_input = self.text_input(id!(main_prompt_input.prompt)); - if !prompt_input.text().is_empty() { - self.enable_prompt_buttons(cx); + let (button_color, prompt_enabled) = if enabled { + (vec3(0.0, 0.0, 0.0), 1.0) } else { - self.disable_prompt_buttons(cx); - } - } - - fn show_prompt_input_stop_button(&mut self, cx: &mut Cx) { - self.button(id!(main_prompt_input.prompt_send_button)) - .set_visible(false); - self.button(id!(main_prompt_input.prompt_stop_button)) - .set_visible(true); - - self.enable_prompt_buttons(cx); - } - - fn enable_prompt_buttons(&mut self, cx: &mut Cx) { - let enabled_color = vec3(0.0, 0.0, 0.0); - let send_button = self.button(id!(main_prompt_input.prompt_send_button)); - send_button.set_enabled(true); - send_button.apply_over( - cx, - live! { - draw_bg: { - color: (enabled_color) - } - }, - ); + // The color code is #D0D5DD + (vec3(0.816, 0.835, 0.867), 0.0) + }; - let stop_button = self.button(id!(main_prompt_input.prompt_stop_button)); - stop_button.set_enabled(true); - stop_button.apply_over( + prompt_input.apply_over( cx, live! { - draw_bg: { - color: (enabled_color) - } + draw_text: { prompt_enabled: (prompt_enabled) } }, ); - } - fn disable_prompt_buttons(&mut self, cx: &mut Cx) { - let disabled_color = vec3(0.816, 0.835, 0.867); // #D0D5DD let send_button = self.button(id!(main_prompt_input.prompt_send_button)); - send_button.set_enabled(true); - send_button.apply_over( - cx, - live! { - draw_bg: { - color: (disabled_color) - } - }, - ); - let stop_button = self.button(id!(main_prompt_input.prompt_stop_button)); - stop_button.set_enabled(false); - stop_button.apply_over( - cx, - live! { - draw_bg: { - color: (disabled_color) - } - }, - ); + match button { + PromptInputButton::Send => { + // The send button is enabled or not based on the prompt input + send_button.set_visible(true); + send_button.set_enabled(enabled); + send_button.apply_over( + cx, + live! { + draw_bg: { + color: (button_color) + } + }, + ); + stop_button.set_visible(false); + } + PromptInputButton::Stop => { + // The stop button is always enabled, when visible + stop_button.set_visible(true); + stop_button.set_enabled(true); + stop_button.apply_over( + cx, + live! { + draw_bg: { + color: #x000 + } + }, + ); + send_button.set_visible(false); + } + } } fn scroll_messages_to_bottom(&mut self, cx: &mut Cx) { @@ -737,7 +729,7 @@ impl ChatPanel { fn handle_prompt_input_actions(&mut self, cx: &mut Cx, actions: &Actions, scope: &mut Scope) { let prompt_input = self.text_input(id!(main_prompt_input.prompt)); if let Some(_text) = prompt_input.changed(actions) { - self.update_prompt_input(cx); + self.redraw(cx); } if self @@ -753,20 +745,32 @@ impl ChatPanel { } fn send_message(&mut self, cx: &mut Cx, scope: &mut Scope, prompt: String) { + // Check if we have any text to send if prompt.trim().is_empty() { return; } - let store = scope.data.get_mut::().unwrap(); - store.chats.send_chat_message(prompt.clone()); + // Let's confirm we're in an appropriate state to send a message + self.update_state(scope); + if matches!( + self.state, + State::ModelSelectedWithChat { + is_streaming: false, + is_loading: false, + .. + } | State::ModelSelectedWithEmptyChat { is_loading: false } + ) { + let store = scope.data.get_mut::().unwrap(); + store.chats.send_chat_message(prompt.clone()); - let prompt_input = self.text_input(id!(main_prompt_input.prompt)); - prompt_input.set_text_and_redraw(cx, ""); - prompt_input.set_cursor(0, 0); - self.update_prompt_input(cx); + let prompt_input = self.text_input(id!(main_prompt_input.prompt)); + prompt_input.set_text_and_redraw(cx, ""); + prompt_input.set_cursor(0, 0); - // Scroll to the bottom when the message is sent - self.scroll_messages_to_bottom(cx); + // Scroll to the bottom when the message is sent + self.scroll_messages_to_bottom(cx); + self.redraw(cx); + } } fn update_view(&mut self, cx: &mut Cx2d, scope: &mut Scope) { @@ -774,7 +778,7 @@ impl ChatPanel { self.update_prompt_input(cx); match self.state { - State::ModelSelectedWithEmptyChat => { + State::ModelSelectedWithEmptyChat { .. } => { let store = scope.data.get::().unwrap(); self.view(id!(empty_conversation)) @@ -809,7 +813,7 @@ impl ChatPanel { no_model.set_visible(true); } - State::ModelSelectedWithEmptyChat => { + State::ModelSelectedWithEmptyChat { .. } => { jump_to_bottom.set_visible(false); no_downloaded_model.set_visible(false); no_model.set_visible(false); diff --git a/src/chat/mod.rs b/src/chat/mod.rs index b9f63d15..6223dbd8 100644 --- a/src/chat/mod.rs +++ b/src/chat/mod.rs @@ -10,6 +10,7 @@ pub mod delete_chat_modal; pub mod model_info; pub mod model_selector; pub mod model_selector_list; +pub mod model_selector_loading; pub mod shared; use makepad_widgets::Cx; @@ -25,6 +26,7 @@ pub fn live_design(cx: &mut Cx) { model_info::live_design(cx); model_selector_list::live_design(cx); model_selector::live_design(cx); + model_selector_loading::live_design(cx); shared::live_design(cx); delete_chat_modal::live_design(cx); chat_history_card_options::live_design(cx); diff --git a/src/chat/model_selector.rs b/src/chat/model_selector.rs index 1083a0d7..1947c845 100644 --- a/src/chat/model_selector.rs +++ b/src/chat/model_selector.rs @@ -4,7 +4,10 @@ use crate::{ }; use makepad_widgets::*; -use super::model_selector_list::{ModelSelectorAction, ModelSelectorListWidgetExt}; +use super::{ + model_selector_list::{ModelSelectorAction, ModelSelectorListWidgetExt}, + model_selector_loading::ModelSelectorLoadingWidgetExt, +}; live_design! { import makepad_widgets::base::*; @@ -14,13 +17,15 @@ live_design! { import crate::chat::model_info::ModelInfo; import crate::chat::model_selector_list::ModelSelectorList; + import crate::chat::model_selector_loading::ModelSelectorLoading; ModelSelectorButton = { width: Fill, height: 54, + flow: Overlay, align: {x: 0.0, y: 0.5}, - padding: 16, + padding: 0, draw_bg: { instance radius: 3.0, @@ -29,11 +34,17 @@ live_design! { cursor: Hand, + loading = { + width: Fill, + height: Fill, + } + choose = { width: Fill, height: Fit, align: {x: 0.5, y: 0.5}, + padding: 16, label =