1use makepad_widgets::defer_with_redraw::DeferWithRedraw;
2use makepad_widgets::*;
3use std::cell::{Ref, RefMut};
4use std::sync::{Arc, Mutex};
5
6use crate::aitk::utils::tool::display_name_from_namespaced;
7use crate::prelude::*;
8use crate::utils::makepad::events::EventExt;
9use crate::widgets::stt_input::*;
10
11pub use crate::widgets::stt_input::SttUtility;
13
14script_mod!(
15 use mod.prelude.widgets.*
16 use mod.widgets.*
17
18 mod.widgets.ChatBase = #(Chat::register_widget(vm))
19 mod.widgets.Chat = set_type_default() do mod.widgets.ChatBase {
20 flow: Down
21 messages := Messages {}
22 prompt := PromptInput {}
23 stt_input := SttInput { visible: false }
24
25 View {
26 width: Fill, height: Fit
27 flow: Overlay
28
29 audio_modal := MolyModal {
30 dismiss_on_focus_lost: false
31 content: RealtimeContent {}
32 }
33 }
34 }
35);
36
37#[derive(Script, ScriptHook, Widget)]
39pub struct Chat {
40 #[deref]
41 deref: View,
42
43 #[rust]
44 chat_controller: Option<Arc<Mutex<ChatController>>>,
45
46 #[live(true)]
49 pub stream: bool,
50
51 #[rust]
52 plugin_id: Option<ChatControllerPluginRegistrationId>,
53}
54
55impl Widget for Chat {
56 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
57 if let Event::AudioDevices(devices) = event {
59 let input = devices.default_input();
60 if !input.is_empty() {
61 cx.use_audio_inputs(&input);
62 }
63 }
64
65 self.ui_runner().handle(cx, event, scope, self);
66 self.deref.handle_event(cx, event, scope);
67
68 self.handle_messages(cx, event);
69 self.handle_prompt_input(cx, event);
70 self.handle_stt_input_actions(cx, event);
71 self.handle_realtime(cx);
72 self.handle_modal_dismissal(cx, event);
73 }
74
75 fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
76 let has_stt = self.stt_input_ref(cx).read().stt_utility().is_some();
77 self.prompt_input_ref(cx)
78 .write()
79 .set_stt_visible(cx, has_stt);
80
81 self.deref.draw_walk(cx, scope, walk)
82 }
83}
84
85impl Chat {
86 pub fn prompt_input_ref(&self, cx: &Cx) -> PromptInputRef {
88 self.prompt_input(cx, ids!(prompt))
89 }
90
91 pub fn messages_ref(&self, cx: &Cx) -> MessagesRef {
93 self.messages(cx, ids!(messages))
94 }
95
96 pub fn stt_input_ref(&self, cx: &Cx) -> SttInputRef {
98 self.stt_input(cx, ids!(stt_input))
99 }
100
101 pub fn set_stt_utility(&mut self, cx: &Cx, utility: Option<SttUtility>) {
103 self.stt_input_ref(cx).write().set_stt_utility(utility);
104 }
105
106 pub fn stt_utility(&self, cx: &Cx) -> Option<SttUtility> {
108 self.stt_input_ref(cx).read().stt_utility().cloned()
109 }
110
111 fn handle_prompt_input(&mut self, cx: &mut Cx, event: &Event) {
112 let submitted = self
113 .prompt_input_ref(cx)
114 .read()
115 .submitted(cx, event.actions());
116 if submitted {
117 self.handle_submit(cx);
118 }
119
120 let call_pressed = self
121 .prompt_input_ref(cx)
122 .read()
123 .call_pressed(cx, event.actions());
124 if call_pressed {
125 self.handle_call(cx);
126 }
127
128 let stt_pressed = self
129 .prompt_input_ref(cx)
130 .read()
131 .stt_pressed(cx, event.actions());
132 if stt_pressed {
133 self.prompt_input_ref(cx).set_visible(cx, false);
134 self.stt_input_ref(cx).set_visible(cx, true);
135 self.stt_input_ref(cx).write().start_recording(cx);
136 self.redraw(cx);
137 }
138 }
139
140 fn handle_stt_input_actions(&mut self, cx: &mut Cx, event: &Event) {
141 let transcription = self.stt_input_ref(cx).read().transcribed(event.actions());
142
143 if let Some(transcription) = transcription {
144 self.stt_input_ref(cx).set_visible(cx, false);
145 self.prompt_input_ref(cx).set_visible(cx, true);
146
147 let mut text = self.prompt_input_ref(cx).text();
148 if let Some(last) = text.as_bytes().last()
149 && *last != b' '
150 {
151 text.push(' ');
152 }
153 text.push_str(&transcription);
154 self.prompt_input_ref(cx).set_text(cx, &text);
155
156 self.prompt_input_ref(cx).redraw(cx);
157 }
158
159 let cancelled = self.stt_input_ref(cx).read().cancelled(event.actions());
160 if cancelled {
161 self.stt_input_ref(cx).set_visible(cx, false);
162 self.prompt_input_ref(cx).set_visible(cx, true);
163 self.prompt_input_ref(cx).redraw(cx);
164 }
165 }
166
167 fn handle_realtime(&mut self, cx: &mut Cx) {
168 if self.realtime(cx, ids!(realtime)).connection_requested()
169 && self
170 .chat_controller
171 .as_ref()
172 .map(|c| c.lock().unwrap().state().bot_id.is_some())
173 .unwrap_or(false)
174 {
175 self.chat_controller
176 .as_mut()
177 .unwrap()
178 .lock()
179 .unwrap()
180 .dispatch_task(ChatTask::Send);
181 }
182 }
183
184 fn handle_modal_dismissal(&mut self, cx: &mut Cx, event: &Event) {
185 for action in event.actions() {
186 if let RealtimeModalAction::DismissModal = action.cast() {
187 self.moly_modal(cx, ids!(audio_modal)).close(cx);
188 }
189 }
190
191 if self
192 .moly_modal(cx, ids!(audio_modal))
193 .dismissed(event.actions())
194 {
195 let mut conversation_messages = self
196 .realtime(cx, ids!(realtime))
197 .take_conversation_messages();
198
199 self.realtime(cx, ids!(realtime)).reset_state(cx);
200
201 if !conversation_messages.is_empty() {
202 let chat_controller = self.chat_controller.clone().unwrap();
203
204 let mut all_messages = chat_controller.lock().unwrap().state().messages.clone();
205
206 let system_message = Message {
207 from: EntityId::App,
208 content: MessageContent {
209 text: "Voice call started.".to_string(),
210 ..Default::default()
211 },
212 ..Default::default()
213 };
214 conversation_messages.insert(0, system_message);
215
216 let system_message = Message {
217 from: EntityId::App,
218 content: MessageContent {
219 text: "Voice call ended.".to_string(),
220 ..Default::default()
221 },
222 ..Default::default()
223 };
224 conversation_messages.push(system_message);
225
226 all_messages.extend(conversation_messages);
227 chat_controller
228 .lock()
229 .unwrap()
230 .dispatch_mutation(VecMutation::Set(all_messages));
231
232 self.messages_ref(cx).write().instant_scroll_to_bottom(cx);
233 }
234 }
235 }
236
237 fn handle_capabilities(&mut self, cx: &mut Cx) {
238 let capabilities = self.chat_controller.as_ref().and_then(|controller| {
239 let lock = controller.lock().unwrap();
240 let bot_id = lock.state().bot_id.as_ref()?;
241 lock.state()
242 .get_bot(bot_id)
243 .map(|bot| bot.capabilities.clone())
244 });
245
246 self.prompt_input_ref(cx)
247 .write()
248 .set_bot_capabilities(cx, capabilities);
249 }
250
251 fn handle_messages(&mut self, cx: &mut Cx, event: &Event) {
252 for action in event.actions() {
253 let Some(action) = action.as_widget_action() else {
254 continue;
255 };
256
257 if action.widget_uid != self.messages_ref(cx).widget_uid() {
258 continue;
259 }
260
261 let chat_controller = self.chat_controller.clone().unwrap();
262
263 match action.cast::<MessagesAction>() {
264 MessagesAction::Delete(index) => chat_controller
265 .lock()
266 .unwrap()
267 .dispatch_mutation(VecMutation::<Message>::RemoveOne(index)),
268 MessagesAction::Copy(index) => {
269 let lock = chat_controller.lock().unwrap();
270 let text = &lock.state().messages[index].content.text;
271 cx.copy_to_clipboard(text);
272 }
273 MessagesAction::EditSave(index) => {
274 let text = self
275 .messages_ref(cx)
276 .read()
277 .current_editor_text(cx)
278 .expect("no editor text");
279
280 self.messages_ref(cx)
281 .write()
282 .set_message_editor_visibility(index, false);
283
284 let mut lock = chat_controller.lock().unwrap();
285
286 let mutation =
287 VecMutation::update_with(&lock.state().messages, index, |message| {
288 message.update_content(move |content| {
289 content.text = text;
290 });
291 });
292
293 lock.dispatch_mutation(mutation);
294 }
295 MessagesAction::EditRegenerate(index) => {
296 let mut messages =
297 chat_controller.lock().unwrap().state().messages[0..=index].to_vec();
298
299 let text = self
300 .messages_ref(cx)
301 .read()
302 .current_editor_text(cx)
303 .expect("no editor text");
304
305 self.messages_ref(cx)
306 .write()
307 .set_message_editor_visibility(index, false);
308
309 messages[index].update_content(|content| {
310 content.text = text;
311 });
312
313 chat_controller
314 .lock()
315 .unwrap()
316 .dispatch_mutation(VecMutation::Set(messages));
317
318 if self
319 .chat_controller
320 .as_ref()
321 .map(|c| c.lock().unwrap().state().bot_id.is_some())
322 .unwrap_or(false)
323 {
324 chat_controller
325 .lock()
326 .unwrap()
327 .dispatch_task(ChatTask::Send);
328 }
329 }
330 MessagesAction::ToolApprove(index) => {
331 let mut lock = chat_controller.lock().unwrap();
332
333 let mut updated_message = lock.state().messages[index].clone();
334
335 for tool_call in &mut updated_message.content.tool_calls {
336 tool_call.permission_status = ToolCallPermissionStatus::Approved;
337 }
338
339 lock.dispatch_mutation(VecMutation::Update(index, updated_message));
340
341 let tools = lock.state().messages[index].content.tool_calls.clone();
342 let bot_id = lock.state().bot_id.clone();
343 lock.dispatch_task(ChatTask::Execute(tools, bot_id));
344 }
345 MessagesAction::ToolDeny(index) => {
346 let mut lock = chat_controller.lock().unwrap();
347
348 let mut updated_message = lock.state().messages[index].clone();
349
350 updated_message.update_content(|content| {
351 for tool_call in &mut content.tool_calls {
352 tool_call.permission_status = ToolCallPermissionStatus::Denied;
353 }
354 });
355
356 lock.dispatch_mutation(VecMutation::Update(index, updated_message));
357
358 let tool_results: Vec<ToolResult> = lock.state().messages[index]
359 .content
360 .tool_calls
361 .iter()
362 .map(|tc| {
363 let display_name = display_name_from_namespaced(&tc.name);
364 ToolResult {
365 tool_call_id: tc.id.clone(),
366 content: format!(
367 "Tool execution was denied by the user. \
368 Tool '{}' was not executed.",
369 display_name
370 ),
371 is_error: true,
372 }
373 })
374 .collect();
375
376 lock.dispatch_mutation(VecMutation::Push(Message {
377 from: EntityId::Tool,
378 content: MessageContent {
379 text: "\u{1f6ab} Tool execution was denied \
380 by the user."
381 .to_string(),
382 tool_results,
383 ..Default::default()
384 },
385 ..Default::default()
386 }));
387 }
388 MessagesAction::None => {}
389 }
390 }
391 }
392
393 fn handle_submit(&mut self, cx: &mut Cx) {
394 let mut prompt = self.prompt_input_ref(cx);
395 let chat_controller = self.chat_controller.clone().unwrap();
396
397 if prompt.read().has_send_task()
398 && self
399 .chat_controller
400 .as_ref()
401 .map(|c| c.lock().unwrap().state().bot_id.is_some())
402 .unwrap_or(false)
403 {
404 let text = prompt.text();
405 let attachments = prompt
406 .read()
407 .attachment_list_ref(cx)
408 .read()
409 .attachments
410 .clone();
411
412 if !text.is_empty() || !attachments.is_empty() {
413 chat_controller
414 .lock()
415 .unwrap()
416 .dispatch_mutation(VecMutation::Push(Message {
417 from: EntityId::User,
418 content: MessageContent {
419 text,
420 attachments,
421 ..Default::default()
422 },
423 ..Default::default()
424 }));
425 }
426
427 prompt.write().reset(cx);
428 chat_controller
429 .lock()
430 .unwrap()
431 .dispatch_task(ChatTask::Send);
432 } else if prompt.read().has_stop_task() {
433 chat_controller
434 .lock()
435 .unwrap()
436 .dispatch_task(ChatTask::Stop);
437 }
438 }
439
440 fn handle_call(&mut self, _cx: &mut Cx) {
441 if self
442 .chat_controller
443 .as_ref()
444 .map(|c| c.lock().unwrap().state().bot_id.is_some())
445 .unwrap_or(false)
446 {
447 self.chat_controller
448 .as_mut()
449 .unwrap()
450 .lock()
451 .unwrap()
452 .dispatch_task(ChatTask::Send);
453 }
454 }
455
456 pub fn is_streaming(&self) -> bool {
458 self.chat_controller
459 .as_ref()
460 .unwrap()
461 .lock()
462 .unwrap()
463 .state()
464 .is_streaming
465 }
466
467 pub fn set_chat_controller(
469 &mut self,
470 cx: &mut Cx,
471 chat_controller: Option<Arc<Mutex<ChatController>>>,
472 ) {
473 if self.chat_controller.as_ref().map(Arc::as_ptr)
474 == chat_controller.as_ref().map(Arc::as_ptr)
475 {
476 return;
477 }
478
479 self.unlink_current_controller();
480 self.chat_controller = chat_controller;
481
482 self.messages_ref(cx).write().chat_controller = self.chat_controller.clone();
483 self.realtime(cx, ids!(realtime))
484 .set_chat_controller(self.chat_controller.clone());
485 self.prompt_input_ref(cx)
486 .write()
487 .set_chat_controller(cx, self.chat_controller.clone());
488
489 if let Some(controller) = self.chat_controller.as_ref() {
490 let mut guard = controller.lock().unwrap();
491
492 let plugin = Plugin::new(self.ui_runner());
493 self.plugin_id = Some(guard.append_plugin(plugin));
494 }
495 }
496
497 pub fn chat_controller(&self) -> Option<&Arc<Mutex<ChatController>>> {
499 self.chat_controller.as_ref()
500 }
501
502 fn unlink_current_controller(&mut self) {
503 if let Some(plugin_id) = self.plugin_id {
504 if let Some(controller) = self.chat_controller.as_ref() {
505 controller.lock().unwrap().remove_plugin(plugin_id);
506 }
507 }
508
509 self.chat_controller = None;
510 self.plugin_id = None;
511 }
512
513 fn handle_streaming_start(&mut self, cx: &mut Cx) {
514 self.prompt_input_ref(cx).write().set_stop();
515 self.messages_ref(cx).write().animated_scroll_to_bottom(cx);
516 self.redraw(cx);
517 }
518
519 fn handle_streaming_end(&mut self, cx: &mut Cx) {
520 self.prompt_input_ref(cx).write().set_send();
521 self.redraw(cx);
522 }
523}
524
525impl ChatRef {
528 pub fn read(&self) -> Ref<'_, Chat> {
532 self.borrow().unwrap()
533 }
534
535 pub fn write(&mut self) -> RefMut<'_, Chat> {
539 self.borrow_mut().unwrap()
540 }
541
542 pub fn read_with<R>(&self, f: impl FnOnce(&Chat) -> R) -> R {
546 f(&*self.read())
547 }
548
549 pub fn write_with<R>(&mut self, f: impl FnOnce(&mut Chat) -> R) -> R {
553 f(&mut *self.write())
554 }
555}
556
557impl Drop for Chat {
558 fn drop(&mut self) {
559 self.unlink_current_controller();
560 }
561}
562
563struct Plugin {
564 ui: UiRunner<Chat>,
565}
566
567impl Plugin {
568 fn new(ui: UiRunner<Chat>) -> Self {
569 Self { ui }
570 }
571}
572
573impl ChatControllerPlugin for Plugin {
574 fn on_state_ready(&mut self, _state: &ChatState, mutations: &[ChatStateMutation]) {
575 for mutation in mutations {
576 match mutation {
577 ChatStateMutation::SetIsStreaming(true) => {
578 self.ui.defer(|chat, cx, _| {
579 chat.handle_streaming_start(cx);
580 });
581 }
582 ChatStateMutation::SetIsStreaming(false) => {
583 self.ui.defer(|chat, cx, _| {
584 chat.handle_streaming_end(cx);
585 });
586 }
587 ChatStateMutation::MutateBots(_) => {
588 self.ui.defer(|chat, cx, _| {
589 if let Some(controller) = &chat.chat_controller {
590 let mut lock = controller.lock().unwrap();
591 if let Some(bot_id) = lock.state().bot_id.clone() {
592 let bot_still_available =
593 lock.state().bots.iter().any(|b| &b.id == &bot_id);
594 if !bot_still_available {
595 lock.dispatch_mutation(ChatStateMutation::SetBotId(None));
596 }
597 }
598 }
599
600 chat.handle_capabilities(cx);
601 });
602 }
603 ChatStateMutation::SetBotId(_bot_id) => {
604 self.ui.defer(move |chat, cx, _| {
605 chat.handle_capabilities(cx);
606 });
607 }
608 _ => {}
609 }
610 }
611
612 self.ui.defer_with_redraw(move |_, _, _| {});
614 }
615
616 fn on_upgrade(&mut self, upgrade: Upgrade, bot_id: &BotId) -> Option<Upgrade> {
617 match upgrade {
618 Upgrade::Realtime(channel) => {
619 let entity_id = EntityId::Bot(bot_id.clone());
620 self.ui.defer(move |me, cx, _| {
621 me.handle_streaming_end(cx);
622
623 let mut realtime = me.realtime(cx, ids!(realtime));
624 realtime.set_bot_entity_id(cx, entity_id);
625 realtime.set_realtime_channel(channel.clone());
626
627 let modal = me.moly_modal(cx, ids!(audio_modal));
628 modal.open_as_dialog(cx);
629 });
630 None
631 }
632 #[allow(unreachable_patterns)]
633 upgrade => Some(upgrade),
634 }
635 }
636}