1use makepad_widgets::*;
2use std::cell::{Ref, RefMut};
3
4#[allow(unused)]
5use crate::{
6 Attachment,
7 protocol::{BotCapabilities, BotCapability},
8 utils::makepad::EventExt,
9 widgets::attachment_list::{AttachmentListRef, AttachmentListWidgetExt},
10};
11
12live_design! {
13 use link::theme::*;
14 use link::widgets::*;
15 use link::moly_kit_theme::*;
16 use link::shaders::*;
17
18 use crate::widgets::attachment_list::*;
19
20 pub PromptInput = {{PromptInput}} <CommandTextInput> {
21 send_icon: dep("crate://self/resources/send.svg"),
22 stop_icon: dep("crate://self/resources/stop.svg"),
23
24 height: 80
25 persistent = {
26 height: Fill
27 padding: {top: 8, bottom: 6, left: 4, right: 10}
28 draw_bg: {
29 color: #fff,
30 border_radius: 10.0,
31 border_color: #D0D5DD,
32 border_size: 1.0,
33 }
34 center = {
35 height: Fill
36 left = {
37 attach = <Button> {
38 visible: false
39 text: "",
40 draw_text: {
41 text_style: <THEME_FONT_ICONS> {
42 font_size: 16.
43 }
44 color: #000,
45 color_hover: #000,
46 color_focus: #000
47 color_down: #000
48 }
49 draw_bg: {
50 color_down: #0000
51 border_radius: 7.
52 border_size: 0.
53 }
54 }
55 }
56 text_input = {
57 height: Fill
58 empty_text: "Start typing...",
59 draw_bg: {
60 fn pixel(self) -> vec4 {
61 return vec4(0.);
62 }
63 }
64 draw_text: {
65 color: #000
66 color_hover: #000
67 color_focus: #000
68 color_empty: #98A2B3
69 color_empty_focus: #98A2B3
70 text_style: {font_size: 11}
71 }
72 draw_selection: {
73 color: #d9e7e9
74 color_hover: #d9e7e9
75 color_focus: #d9e7e9
76 }
77 draw_cursor: {
78 fn pixel(self) -> vec4 {
79 return #bbb;
80 }
81 }
82 }
83 right = {
84 align: {x: 0.5, y: 0.5}
85 spacing: 5
86 audio = <Button> {
87 visible: false
88 text: ""
89 draw_text: {
90 text_style: <THEME_FONT_ICONS> {
91 font_size: 16.
92 }
93 color: #000,
94 color_hover: #000,
95 color_focus: #000
96 color_down: #000
97 }
98 draw_bg: {
99 color_down: #0000
100 border_radius: 7.
101 border_size: 0.
102 }
103 }
104 submit = <Button> {
105 width: 28,
106 height: 28,
107 padding: {right: 2},
108 margin: {bottom: 2},
109
110 draw_icon: {
111 color: #fff
112 }
113
114 draw_bg: {
115 fn get_color(self) -> vec4 {
116 if self.enabled == 0.0 {
117 return #D0D5DD;
118 }
119
120 return #000;
121 }
122
123 fn pixel(self) -> vec4 {
124 let sdf = Sdf2d::viewport(self.pos * self.rect_size);
125 let center = self.rect_size * 0.5;
126 let radius = min(self.rect_size.x, self.rect_size.y) * 0.5;
127
128 sdf.circle(center.x, center.y, radius);
129 sdf.fill_keep(self.get_color());
130
131 return sdf.result
132 }
133 }
134 icon_walk: {
135 width: 12,
136 height: 12
137 margin: {top: 0, left: 2},
138 }
139 }
140 }
141 }
142 bottom = {
143 attachments = <DenseAttachmentList> {
144 wrapper = {
145 margin: { top: 6 }
146 }
147 }
148 }
149 }
150 }
151}
152
153#[derive(Default, Copy, Clone, PartialEq)]
154pub enum Task {
155 #[default]
156 Send,
157 Stop,
158}
159
160#[derive(Default, Copy, Clone, PartialEq)]
161pub enum Interactivity {
162 #[default]
163 Enabled,
164 Disabled,
165}
166
167#[derive(Live, Widget)]
171pub struct PromptInput {
172 #[deref]
173 deref: CommandTextInput,
174
175 #[live]
177 pub send_icon: LiveValue,
178
179 #[live]
181 pub stop_icon: LiveValue,
182
183 #[rust]
185 pub task: Task,
186
187 #[rust]
189 pub interactivity: Interactivity,
190
191 #[rust]
193 pub bot_capabilities: Option<BotCapabilities>,
194}
195
196impl LiveHook for PromptInput {
197 #[allow(unused)]
198 fn after_new_from_doc(&mut self, cx: &mut Cx) {
199 self.update_button_visibility(cx);
200 }
201}
202
203impl Widget for PromptInput {
204 fn set_text(&mut self, cx: &mut Cx, v: &str) {
205 self.deref.set_text(cx, v);
206 }
207
208 fn text(&self) -> String {
209 self.deref.text()
210 }
211
212 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
213 self.deref.handle_event(cx, event, scope);
214 self.ui_runner().handle(cx, event, scope, self);
215
216 if self.button(id!(attach)).clicked(event.actions()) {
217 let ui = self.ui_runner();
218 Attachment::pick_multiple(move |result| match result {
219 Ok(attachments) => {
220 ui.defer_with_redraw(move |me, _, _| {
221 let mut list = me.attachment_list_ref();
222 list.write().attachments.extend(attachments);
223 list.write().on_tap(move |list, index| {
224 list.attachments.remove(index);
225 });
226 });
227 }
228 Err(_) => {}
229 });
230 }
231 }
232
233 fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
234 let button = self.button(id!(submit));
235
236 match self.task {
237 Task::Send => {
238 button.apply_over(
239 cx,
240 live! {
241 draw_icon: {
242 svg_file: (self.send_icon),
243 }
244 },
245 );
246 }
247 Task::Stop => {
248 button.apply_over(
249 cx,
250 live! {
251 draw_icon: {
252 svg_file: (self.stop_icon),
253 }
254 },
255 );
256 }
257 }
258
259 match self.interactivity {
260 Interactivity::Enabled => {
261 button.apply_over(
262 cx,
263 live! {
264 draw_bg: {
265 enabled: 1.0
266 }
267 },
268 );
269 button.set_enabled(cx, true);
270 }
271 Interactivity::Disabled => {
272 button.apply_over(
273 cx,
274 live! {
275 draw_bg: {
276 enabled: 0.0
277 }
278 },
279 );
280 button.set_enabled(cx, false);
281 }
282 }
283
284 self.deref.draw_walk(cx, scope, walk)
285 }
286}
287
288impl PromptInput {
289 pub fn reset(&mut self, cx: &mut Cx) {
293 self.deref.reset(cx);
294 self.attachment_list_ref().write().attachments.clear();
295 }
296
297 pub fn submitted(&self, actions: &Actions) -> bool {
302 let submit = self.button(id!(submit));
303 let input = self.text_input_ref();
304 (submit.clicked(actions) || input.returned(actions).is_some())
305 && self.interactivity == Interactivity::Enabled
306 }
307
308 pub fn call_pressed(&self, actions: &Actions) -> bool {
309 self.button(id!(audio)).clicked(actions)
310 }
311
312 pub fn has_send_task(&self) -> bool {
314 self.task == Task::Send
315 }
316
317 pub fn has_stop_task(&self) -> bool {
319 self.task == Task::Stop
320 }
321
322 pub fn enable(&mut self) {
324 self.interactivity = Interactivity::Enabled;
325 }
326
327 pub fn disable(&mut self) {
329 self.interactivity = Interactivity::Disabled;
330 }
331
332 pub fn set_send(&mut self) {
334 self.task = Task::Send;
335 }
336
337 pub fn set_stop(&mut self) {
339 self.task = Task::Stop;
340 }
341
342 pub(crate) fn attachment_list_ref(&self) -> AttachmentListRef {
343 self.attachment_list(id!(attachments))
344 }
345
346 pub fn set_bot_capabilities(&mut self, cx: &mut Cx, capabilities: Option<BotCapabilities>) {
348 self.bot_capabilities = capabilities;
349 self.update_button_visibility(cx);
350 }
351
352 fn update_button_visibility(&mut self, cx: &mut Cx) {
354 let supports_attachments = self
355 .bot_capabilities
356 .as_ref()
357 .map(|caps| caps.supports_attachments())
358 .unwrap_or(false);
359
360 let supports_realtime = self
361 .bot_capabilities
362 .as_ref()
363 .map(|caps| caps.supports_realtime())
364 .unwrap_or(false);
365
366 #[cfg(any(
368 target_os = "windows",
369 target_os = "macos",
370 target_os = "linux",
371 target_arch = "wasm32"
372 ))]
373 self.button(id!(attach))
374 .set_visible(cx, supports_attachments);
375
376 #[cfg(not(any(
377 target_os = "windows",
378 target_os = "macos",
379 target_os = "linux",
380 target_arch = "wasm32"
381 )))]
382 self.button(id!(attach)).set_visible(cx, false);
383
384 #[cfg(not(target_arch = "wasm32"))]
387 #[cfg(feature = "realtime")]
388 self.button(id!(audio)).set_visible(cx, supports_realtime);
389
390 if supports_realtime {
391 self.interactivity = Interactivity::Disabled;
392 self.text_input_ref().set_is_read_only(cx, true);
393 self.text_input_ref()
394 .set_text(cx, "For realtime models, use the audio feature ->");
395 self.redraw(cx);
396 } else {
397 self.interactivity = Interactivity::Enabled;
398 self.text_input_ref().set_is_read_only(cx, false);
399 self.text_input_ref().set_text(cx, "");
400 self.redraw(cx);
401 }
402 }
403}
404
405impl PromptInputRef {
406 pub fn read(&self) -> Ref<'_, PromptInput> {
410 self.borrow().unwrap()
411 }
412
413 pub fn write(&mut self) -> RefMut<'_, PromptInput> {
417 self.borrow_mut().unwrap()
418 }
419
420 pub fn read_with<R>(&self, f: impl FnOnce(&PromptInput) -> R) -> R {
424 f(&*self.read())
425 }
426
427 pub fn write_with<R>(&mut self, f: impl FnOnce(&mut PromptInput) -> R) -> R {
431 f(&mut *self.write())
432 }
433}