Skip to content

Commit 4e21a59

Browse files
committed
Visual indication when loading models
1 parent d645101 commit 4e21a59

File tree

6 files changed

+331
-40
lines changed

6 files changed

+331
-40
lines changed

src/chat/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod delete_chat_modal;
1010
pub mod model_info;
1111
pub mod model_selector;
1212
pub mod model_selector_list;
13+
pub mod model_selector_loading;
1314
pub mod shared;
1415

1516
use makepad_widgets::Cx;
@@ -25,6 +26,7 @@ pub fn live_design(cx: &mut Cx) {
2526
model_info::live_design(cx);
2627
model_selector_list::live_design(cx);
2728
model_selector::live_design(cx);
29+
model_selector_loading::live_design(cx);
2830
shared::live_design(cx);
2931
delete_chat_modal::live_design(cx);
3032
chat_history_card_options::live_design(cx);

src/chat/model_selector.rs

+70-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
};
55
use makepad_widgets::*;
66

7-
use super::model_selector_list::{ModelSelectorAction, ModelSelectorListWidgetExt};
7+
use super::{model_selector_list::{ModelSelectorAction, ModelSelectorListWidgetExt}, model_selector_loading::ModelSelectorLoadingWidgetExt};
88

99
live_design! {
1010
import makepad_widgets::base::*;
@@ -14,13 +14,15 @@ live_design! {
1414

1515
import crate::chat::model_info::ModelInfo;
1616
import crate::chat::model_selector_list::ModelSelectorList;
17+
import crate::chat::model_selector_loading::ModelSelectorLoading;
1718

1819
ModelSelectorButton = <RoundedView> {
1920
width: Fill,
2021
height: 54,
22+
flow: Overlay,
2123

2224
align: {x: 0.0, y: 0.5},
23-
padding: 16,
25+
padding: 0,
2426

2527
draw_bg: {
2628
instance radius: 3.0,
@@ -29,11 +31,18 @@ live_design! {
2931

3032
cursor: Hand,
3133

34+
loading = <ModelSelectorLoading> {
35+
width: Fill,
36+
height: Fill,
37+
visible: false,
38+
}
39+
3240
choose = <View> {
3341
width: Fill,
3442
height: Fit,
3543

3644
align: {x: 0.5, y: 0.5},
45+
padding: 16,
3746

3847
label = <Label> {
3948
draw_text:{
@@ -49,6 +58,8 @@ live_design! {
4958
show_bg: false,
5059
visible: false,
5160

61+
padding: 16,
62+
5263
label = {
5364
draw_text: {
5465
text_style: <BOLD_FONT>{font_size: 11},
@@ -139,6 +150,41 @@ impl Widget for ModelSelector {
139150
self.view.handle_event(cx, event, scope);
140151
self.widget_match_event(cx, event, scope);
141152

153+
let store = scope.data.get::<Store>().unwrap();
154+
155+
if let Hit::FingerDown(fd) =
156+
event.hits_with_capture_overload(cx, self.view(id!(button)).area(), true)
157+
{
158+
if !options_to_display(store) {
159+
return;
160+
};
161+
if fd.tap_count == 1 {
162+
self.open = !self.open;
163+
164+
if self.open {
165+
let list = self.model_selector_list(id!(options.list_container.list));
166+
let height = list.get_height();
167+
if height > MAX_OPTIONS_HEIGHT {
168+
self.options_list_height = Some(MAX_OPTIONS_HEIGHT);
169+
} else {
170+
self.options_list_height = Some(height);
171+
}
172+
173+
self.view(id!(options)).apply_over(
174+
cx,
175+
live! {
176+
height: Fit,
177+
},
178+
);
179+
180+
self.animator_play(cx, id!(open.show));
181+
} else {
182+
self.hide_animation_timer = cx.start_timeout(0.3);
183+
self.animator_play(cx, id!(open.hide));
184+
}
185+
}
186+
}
187+
142188
if self.hide_animation_timer.is_event(event).is_some() {
143189
// When closing animation is done, hide the wrapper element
144190
self.view(id!(options)).apply_over(cx, live! { height: 0 });
@@ -181,6 +227,7 @@ impl Widget for ModelSelector {
181227
}
182228
},
183229
);
230+
self.model_selector_loading(id!(loading)).hide(cx);
184231
} else if no_active_model(store) {
185232
choose_label.set_text("Choose a Model");
186233
let color = vec3(0.0, 0.0, 0.0);
@@ -192,6 +239,7 @@ impl Widget for ModelSelector {
192239
}
193240
},
194241
);
242+
self.model_selector_loading(id!(loading)).hide(cx);
195243
} else {
196244
self.update_selected_model_info(cx, store);
197245
}
@@ -260,6 +308,25 @@ impl ModelSelector {
260308
}
261309

262310
fn update_selected_model_info(&mut self, cx: &mut Cx, store: &Store) {
311+
if let Some(loader) = &store.chats.model_loader {
312+
if !loader.complete {
313+
let caption = format!("Loading {}", loader.file.name);
314+
self.model_selector_loading(id!(loading)).show_and_animate(cx);
315+
self.view(id!(selected)).apply_over(
316+
cx,
317+
live! {
318+
visible: true
319+
label = { text: (caption) }
320+
architecture_tag = { visible: false }
321+
params_size_tag = { visible: false }
322+
file_size_tag = { visible: false }
323+
},
324+
);
325+
self.redraw(cx);
326+
return;
327+
}
328+
}
329+
263330
let Some(downloaded_file) = store.get_loaded_downloaded_file() else {
264331
return;
265332
};
@@ -281,6 +348,7 @@ impl ModelSelector {
281348
let size = format_model_size(&downloaded_file.file.size).unwrap_or("".to_string());
282349
let size_visible = !size.trim().is_empty();
283350

351+
self.model_selector_loading(id!(loading)).hide(cx);
284352
self.view(id!(selected)).apply_over(
285353
cx,
286354
live! {

src/chat/model_selector_loading.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use makepad_widgets::*;
2+
3+
live_design! {
4+
import makepad_widgets::base::*;
5+
import makepad_widgets::theme_desktop_dark::*;
6+
import makepad_draw::shader::std::*;
7+
8+
import crate::shared::styles::*;
9+
import crate::shared::widgets::*;
10+
import crate::landing::model_card::ModelCard;
11+
12+
ANIMATION_SPEED = 1.5;
13+
14+
Bar = <RoundedView> {
15+
width: Fill,
16+
height: 8,
17+
show_bg: true,
18+
draw_bg: {
19+
instance radius: 1.0,
20+
instance dither: 0.9
21+
22+
fn get_color(self) -> vec4 {
23+
return mix(
24+
#F3FFA2,
25+
#E3FBFF,
26+
self.pos.x + self.dither
27+
)
28+
}
29+
30+
fn pixel(self) -> vec4 {
31+
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
32+
sdf.box(
33+
self.inset.x + self.border_width,
34+
self.inset.y + self.border_width,
35+
self.rect_size.x - (self.inset.x + self.inset.z + self.border_width * 2.0),
36+
self.rect_size.y - (self.inset.y + self.inset.w + self.border_width * 2.0),
37+
max(1.0, self.radius)
38+
)
39+
sdf.fill_keep(self.get_color())
40+
if self.border_width > 0.0 {
41+
sdf.stroke(self.get_border_color(), self.border_width)
42+
}
43+
return sdf.result;
44+
}
45+
}
46+
}
47+
48+
ModelSelectorLoading = {{ModelSelectorLoading}} {
49+
width: Fill,
50+
height: Fill,
51+
align: {x: 0, y: 1},
52+
53+
line = <Bar> {}
54+
55+
animator: {
56+
line = {
57+
default: restart,
58+
restart = {
59+
redraw: true,
60+
from: {all: Forward {duration: (ANIMATION_SPEED)}}
61+
apply: {line = { draw_bg: {dither: 0.6} }}
62+
}
63+
run = {
64+
redraw: true,
65+
from: {all: Forward {duration: (ANIMATION_SPEED)}}
66+
apply: {line = { draw_bg: {dither: -0.3} }}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
#[derive(Live, LiveHook, Widget)]
74+
pub struct ModelSelectorLoading {
75+
#[deref]
76+
view: View,
77+
78+
#[animator]
79+
animator: Animator,
80+
81+
#[rust]
82+
timer: Timer,
83+
}
84+
85+
impl Widget for ModelSelectorLoading {
86+
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
87+
if self.timer.is_event(event).is_some() {
88+
self.update_animation(cx);
89+
}
90+
if self.animator_handle_event(cx, event).must_redraw() {
91+
self.redraw(cx);
92+
}
93+
94+
self.view.handle_event(cx, event, scope);
95+
}
96+
97+
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
98+
self.view.draw_walk(cx, scope, walk)
99+
}
100+
}
101+
102+
impl ModelSelectorLoading {
103+
pub fn update_animation(&mut self, cx: &mut Cx) {
104+
if self.animator_in_state(cx, id!(line.restart)) {
105+
self.visible = true;
106+
self.animator_play(cx, id!(line.run));
107+
self.timer = cx.start_timeout(1.5);
108+
} else {
109+
self.visible = true;
110+
self.animator_play(cx, id!(line.restart));
111+
self.timer = cx.start_timeout(1.5);
112+
}
113+
}
114+
}
115+
116+
impl ModelSelectorLoadingRef {
117+
pub fn show_and_animate(&mut self, cx: &mut Cx) {
118+
let Some(mut inner) = self.borrow_mut() else {
119+
return;
120+
};
121+
if inner.timer.is_empty() {
122+
inner.timer = cx.start_timeout(0.2);
123+
}
124+
}
125+
126+
pub fn hide(&mut self, cx: &mut Cx) {
127+
let Some(mut inner) = self.borrow_mut() else {
128+
return;
129+
};
130+
inner.view.visible = false;
131+
inner.timer = Timer::default();
132+
}
133+
}

src/data/chats/mod.rs

+30-37
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
pub mod chat;
2+
pub mod model_loader;
23

34
use anyhow::{Context, Result};
45
use chat::{Chat, ChatID};
6+
use model_loader::ModelLoader;
57
use moxin_backend::Backend;
6-
use moxin_protocol::protocol::{Command, LoadModelResponse};
7-
use moxin_protocol::{data::*, protocol::LoadModelOptions};
8+
use moxin_protocol::data::*;
9+
use moxin_protocol::protocol::Command;
810
use std::fs;
9-
use std::{cell::RefCell, path::PathBuf, rc::Rc, sync::mpsc::channel};
11+
use std::sync::mpsc::channel;
12+
use std::{cell::RefCell, path::PathBuf, rc::Rc};
1013

1114
use super::filesystem::setup_chats_folder;
1215

1316
pub struct Chats {
1417
pub backend: Rc<Backend>,
1518
pub saved_chats: Vec<RefCell<Chat>>,
19+
1620
pub loaded_model: Option<File>,
21+
pub model_loader: Option<ModelLoader>,
1722

1823
current_chat_id: Option<ChatID>,
1924
chats_dir: PathBuf,
@@ -26,6 +31,7 @@ impl Chats {
2631
saved_chats: Vec::new(),
2732
current_chat_id: None,
2833
loaded_model: None,
34+
model_loader: None,
2935
chats_dir: setup_chats_folder(),
3036
}
3137
}
@@ -51,41 +57,28 @@ impl Chats {
5157
.map(|c| c.borrow().id)
5258
}
5359

54-
pub fn load_model(&mut self, file: &File) -> Result<()> {
55-
let (tx, rx) = channel();
56-
let cmd = Command::LoadModel(
57-
file.id.clone(),
58-
LoadModelOptions {
59-
prompt_template: None,
60-
gpu_layers: moxin_protocol::protocol::GPULayers::Max,
61-
use_mlock: false,
62-
rope_freq_scale: 0.0,
63-
rope_freq_base: 0.0,
64-
context_overflow_policy:
65-
moxin_protocol::protocol::ContextOverflowPolicy::StopAtLimit,
66-
},
67-
tx,
68-
);
69-
70-
self.backend.as_ref().command_sender.send(cmd).unwrap();
71-
72-
if let Ok(response) = rx.recv() {
73-
match response {
74-
Ok(LoadModelResponse::Completed(_)) => {
75-
self.loaded_model = Some(file.clone());
76-
Ok(())
77-
}
78-
Ok(_) => {
79-
eprintln!("Error loading model: Unexpected response");
80-
Err(anyhow::anyhow!("Error loading model: Unexpected response"))
81-
}
82-
Err(err) => {
83-
eprintln!("Error loading model: {:?}", err);
84-
Err(err)
85-
}
60+
pub fn load_model(&mut self, file: &File) {
61+
if let Some(loader) = &self.model_loader {
62+
if !loader.complete {
63+
return;
64+
}
65+
}
66+
67+
let loader = ModelLoader::new(file.clone());
68+
loader.load_model(self.backend.as_ref());
69+
self.model_loader = Some(loader);
70+
}
71+
72+
pub fn update_load_model(&mut self) {
73+
let loader = self.model_loader.as_mut();
74+
if let Some(loader) = loader {
75+
if loader.check_load_response().is_ok() {
76+
self.loaded_model = Some(loader.file.clone());
77+
}
78+
79+
if loader.complete {
80+
self.model_loader = None;
8681
}
87-
} else {
88-
Err(anyhow::anyhow!("Error loading model"))
8982
}
9083
}
9184

0 commit comments

Comments
 (0)