active_thread.rs

  1use std::sync::Arc;
  2
  3use collections::HashMap;
  4use editor::{Editor, MultiBuffer};
  5use gpui::{
  6    list, AbsoluteLength, AnyElement, App, ClickEvent, DefiniteLength, EdgesRefinement, Empty,
  7    Entity, Focusable, Length, ListAlignment, ListOffset, ListState, StyleRefinement, Subscription,
  8    Task, TextStyleRefinement, UnderlineStyle,
  9};
 10use language::{Buffer, LanguageRegistry};
 11use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
 12use markdown::{Markdown, MarkdownStyle};
 13use scripting_tool::{ScriptingTool, ScriptingToolInput};
 14use settings::Settings as _;
 15use theme::ThemeSettings;
 16use ui::{prelude::*, Disclosure, KeyBinding};
 17use util::ResultExt as _;
 18
 19use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent};
 20use crate::thread_store::ThreadStore;
 21use crate::tool_use::{ToolUse, ToolUseStatus};
 22use crate::ui::ContextPill;
 23
 24pub struct ActiveThread {
 25    language_registry: Arc<LanguageRegistry>,
 26    thread_store: Entity<ThreadStore>,
 27    thread: Entity<Thread>,
 28    save_thread_task: Option<Task<()>>,
 29    messages: Vec<MessageId>,
 30    list_state: ListState,
 31    rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
 32    rendered_scripting_tool_uses: HashMap<LanguageModelToolUseId, Entity<Markdown>>,
 33    editing_message: Option<(MessageId, EditMessageState)>,
 34    expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
 35    last_error: Option<ThreadError>,
 36    _subscriptions: Vec<Subscription>,
 37}
 38
 39struct EditMessageState {
 40    editor: Entity<Editor>,
 41}
 42
 43impl ActiveThread {
 44    pub fn new(
 45        thread: Entity<Thread>,
 46        thread_store: Entity<ThreadStore>,
 47        language_registry: Arc<LanguageRegistry>,
 48        window: &mut Window,
 49        cx: &mut Context<Self>,
 50    ) -> Self {
 51        let subscriptions = vec![
 52            cx.observe(&thread, |_, _, cx| cx.notify()),
 53            cx.subscribe_in(&thread, window, Self::handle_thread_event),
 54        ];
 55
 56        let mut this = Self {
 57            language_registry,
 58            thread_store,
 59            thread: thread.clone(),
 60            save_thread_task: None,
 61            messages: Vec::new(),
 62            rendered_messages_by_id: HashMap::default(),
 63            rendered_scripting_tool_uses: HashMap::default(),
 64            expanded_tool_uses: HashMap::default(),
 65            list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
 66                let this = cx.entity().downgrade();
 67                move |ix, window: &mut Window, cx: &mut App| {
 68                    this.update(cx, |this, cx| this.render_message(ix, window, cx))
 69                        .unwrap()
 70                }
 71            }),
 72            editing_message: None,
 73            last_error: None,
 74            _subscriptions: subscriptions,
 75        };
 76
 77        for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
 78            this.push_message(&message.id, message.text.clone(), window, cx);
 79
 80            for tool_use in thread.read(cx).scripting_tool_uses_for_message(message.id) {
 81                this.render_scripting_tool_use_markdown(
 82                    tool_use.id.clone(),
 83                    tool_use.name.as_ref(),
 84                    tool_use.input.clone(),
 85                    window,
 86                    cx,
 87                );
 88            }
 89        }
 90
 91        this
 92    }
 93
 94    pub fn thread(&self) -> &Entity<Thread> {
 95        &self.thread
 96    }
 97
 98    pub fn is_empty(&self) -> bool {
 99        self.messages.is_empty()
100    }
101
102    pub fn summary(&self, cx: &App) -> Option<SharedString> {
103        self.thread.read(cx).summary()
104    }
105
106    pub fn summary_or_default(&self, cx: &App) -> SharedString {
107        self.thread.read(cx).summary_or_default()
108    }
109
110    pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
111        self.last_error.take();
112        self.thread
113            .update(cx, |thread, _cx| thread.cancel_last_completion())
114    }
115
116    pub fn last_error(&self) -> Option<ThreadError> {
117        self.last_error.clone()
118    }
119
120    pub fn clear_last_error(&mut self) {
121        self.last_error.take();
122    }
123
124    fn push_message(
125        &mut self,
126        id: &MessageId,
127        text: String,
128        window: &mut Window,
129        cx: &mut Context<Self>,
130    ) {
131        let old_len = self.messages.len();
132        self.messages.push(*id);
133        self.list_state.splice(old_len..old_len, 1);
134
135        let markdown = self.render_markdown(text.into(), window, cx);
136        self.rendered_messages_by_id.insert(*id, markdown);
137        self.list_state.scroll_to(ListOffset {
138            item_ix: old_len,
139            offset_in_item: Pixels(0.0),
140        });
141    }
142
143    fn edited_message(
144        &mut self,
145        id: &MessageId,
146        text: String,
147        window: &mut Window,
148        cx: &mut Context<Self>,
149    ) {
150        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
151            return;
152        };
153        self.list_state.splice(index..index + 1, 1);
154        let markdown = self.render_markdown(text.into(), window, cx);
155        self.rendered_messages_by_id.insert(*id, markdown);
156    }
157
158    fn deleted_message(&mut self, id: &MessageId) {
159        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
160            return;
161        };
162        self.messages.remove(index);
163        self.list_state.splice(index..index + 1, 0);
164        self.rendered_messages_by_id.remove(id);
165    }
166
167    fn render_markdown(
168        &self,
169        text: SharedString,
170        window: &Window,
171        cx: &mut Context<Self>,
172    ) -> Entity<Markdown> {
173        let theme_settings = ThemeSettings::get_global(cx);
174        let colors = cx.theme().colors();
175        let ui_font_size = TextSize::Default.rems(cx);
176        let buffer_font_size = TextSize::Small.rems(cx);
177        let mut text_style = window.text_style();
178
179        text_style.refine(&TextStyleRefinement {
180            font_family: Some(theme_settings.ui_font.family.clone()),
181            font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
182            font_features: Some(theme_settings.ui_font.features.clone()),
183            font_size: Some(ui_font_size.into()),
184            color: Some(cx.theme().colors().text),
185            ..Default::default()
186        });
187
188        let markdown_style = MarkdownStyle {
189            base_text_style: text_style,
190            syntax: cx.theme().syntax().clone(),
191            selection_background_color: cx.theme().players().local().selection,
192            code_block_overflow_x_scroll: true,
193            table_overflow_x_scroll: true,
194            code_block: StyleRefinement {
195                margin: EdgesRefinement {
196                    top: Some(Length::Definite(rems(0.).into())),
197                    left: Some(Length::Definite(rems(0.).into())),
198                    right: Some(Length::Definite(rems(0.).into())),
199                    bottom: Some(Length::Definite(rems(0.5).into())),
200                },
201                padding: EdgesRefinement {
202                    top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
203                    left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
204                    right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
205                    bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
206                },
207                background: Some(colors.editor_background.into()),
208                border_color: Some(colors.border_variant),
209                border_widths: EdgesRefinement {
210                    top: Some(AbsoluteLength::Pixels(Pixels(1.))),
211                    left: Some(AbsoluteLength::Pixels(Pixels(1.))),
212                    right: Some(AbsoluteLength::Pixels(Pixels(1.))),
213                    bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
214                },
215                text: Some(TextStyleRefinement {
216                    font_family: Some(theme_settings.buffer_font.family.clone()),
217                    font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
218                    font_features: Some(theme_settings.buffer_font.features.clone()),
219                    font_size: Some(buffer_font_size.into()),
220                    ..Default::default()
221                }),
222                ..Default::default()
223            },
224            inline_code: TextStyleRefinement {
225                font_family: Some(theme_settings.buffer_font.family.clone()),
226                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
227                font_features: Some(theme_settings.buffer_font.features.clone()),
228                font_size: Some(buffer_font_size.into()),
229                background_color: Some(colors.editor_foreground.opacity(0.1)),
230                ..Default::default()
231            },
232            link: TextStyleRefinement {
233                background_color: Some(colors.editor_foreground.opacity(0.025)),
234                underline: Some(UnderlineStyle {
235                    color: Some(colors.text_accent.opacity(0.5)),
236                    thickness: px(1.),
237                    ..Default::default()
238                }),
239                ..Default::default()
240            },
241            ..Default::default()
242        };
243
244        cx.new(|cx| {
245            Markdown::new(
246                text,
247                markdown_style,
248                Some(self.language_registry.clone()),
249                None,
250                cx,
251            )
252        })
253    }
254
255    /// Renders the input of a scripting tool use to Markdown.
256    ///
257    /// Does nothing if the tool use does not correspond to the scripting tool.
258    fn render_scripting_tool_use_markdown(
259        &mut self,
260        tool_use_id: LanguageModelToolUseId,
261        tool_name: &str,
262        tool_input: serde_json::Value,
263        window: &mut Window,
264        cx: &mut Context<Self>,
265    ) {
266        if tool_name != ScriptingTool::NAME {
267            return;
268        }
269
270        let lua_script = serde_json::from_value::<ScriptingToolInput>(tool_input)
271            .map(|input| input.lua_script)
272            .unwrap_or_default();
273
274        let lua_script =
275            self.render_markdown(format!("```lua\n{lua_script}\n```").into(), window, cx);
276
277        self.rendered_scripting_tool_uses
278            .insert(tool_use_id, lua_script);
279    }
280
281    fn handle_thread_event(
282        &mut self,
283        _thread: &Entity<Thread>,
284        event: &ThreadEvent,
285        window: &mut Window,
286        cx: &mut Context<Self>,
287    ) {
288        match event {
289            ThreadEvent::ShowError(error) => {
290                self.last_error = Some(error.clone());
291            }
292            ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => {
293                self.save_thread(cx);
294            }
295            ThreadEvent::StreamedAssistantText(message_id, text) => {
296                if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
297                    markdown.update(cx, |markdown, cx| {
298                        markdown.append(text, cx);
299                    });
300                }
301            }
302            ThreadEvent::MessageAdded(message_id) => {
303                if let Some(message_text) = self
304                    .thread
305                    .read(cx)
306                    .message(*message_id)
307                    .map(|message| message.text.clone())
308                {
309                    self.push_message(message_id, message_text, window, cx);
310                }
311
312                self.save_thread(cx);
313                cx.notify();
314            }
315            ThreadEvent::MessageEdited(message_id) => {
316                if let Some(message_text) = self
317                    .thread
318                    .read(cx)
319                    .message(*message_id)
320                    .map(|message| message.text.clone())
321                {
322                    self.edited_message(message_id, message_text, window, cx);
323                }
324
325                self.save_thread(cx);
326                cx.notify();
327            }
328            ThreadEvent::MessageDeleted(message_id) => {
329                self.deleted_message(message_id);
330                self.save_thread(cx);
331                cx.notify();
332            }
333            ThreadEvent::UsePendingTools => {
334                self.thread.update(cx, |thread, cx| {
335                    thread.use_pending_tools(cx);
336                });
337            }
338            ThreadEvent::ToolFinished {
339                pending_tool_use, ..
340            } => {
341                if let Some(tool_use) = pending_tool_use {
342                    self.render_scripting_tool_use_markdown(
343                        tool_use.id.clone(),
344                        tool_use.name.as_ref(),
345                        tool_use.input.clone(),
346                        window,
347                        cx,
348                    );
349                }
350
351                if self.thread.read(cx).all_tools_finished() {
352                    let model_registry = LanguageModelRegistry::read_global(cx);
353                    if let Some(model) = model_registry.active_model() {
354                        self.thread.update(cx, |thread, cx| {
355                            thread.send_tool_results_to_model(model, cx);
356                        });
357                    }
358                }
359            }
360        }
361    }
362
363    /// Spawns a task to save the active thread.
364    ///
365    /// Only one task to save the thread will be in flight at a time.
366    fn save_thread(&mut self, cx: &mut Context<Self>) {
367        let thread = self.thread.clone();
368        self.save_thread_task = Some(cx.spawn(|this, mut cx| async move {
369            let task = this
370                .update(&mut cx, |this, cx| {
371                    this.thread_store
372                        .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx))
373                })
374                .ok();
375
376            if let Some(task) = task {
377                task.await.log_err();
378            }
379        }));
380    }
381
382    fn start_editing_message(
383        &mut self,
384        message_id: MessageId,
385        message_text: String,
386        window: &mut Window,
387        cx: &mut Context<Self>,
388    ) {
389        let buffer = cx.new(|cx| {
390            MultiBuffer::singleton(cx.new(|cx| Buffer::local(message_text.clone(), cx)), cx)
391        });
392        let editor = cx.new(|cx| {
393            let mut editor = Editor::new(
394                editor::EditorMode::AutoHeight { max_lines: 8 },
395                buffer,
396                None,
397                false,
398                window,
399                cx,
400            );
401            editor.focus_handle(cx).focus(window);
402            editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
403            editor
404        });
405        self.editing_message = Some((
406            message_id,
407            EditMessageState {
408                editor: editor.clone(),
409            },
410        ));
411        cx.notify();
412    }
413
414    fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
415        self.editing_message.take();
416        cx.notify();
417    }
418
419    fn confirm_editing_message(
420        &mut self,
421        _: &menu::Confirm,
422        _: &mut Window,
423        cx: &mut Context<Self>,
424    ) {
425        let Some((message_id, state)) = self.editing_message.take() else {
426            return;
427        };
428        let edited_text = state.editor.read(cx).text(cx);
429        self.thread.update(cx, |thread, cx| {
430            thread.edit_message(message_id, Role::User, edited_text, cx);
431            for message_id in self.messages_after(message_id) {
432                thread.delete_message(*message_id, cx);
433            }
434        });
435
436        let provider = LanguageModelRegistry::read_global(cx).active_provider();
437        if provider
438            .as_ref()
439            .map_or(false, |provider| provider.must_accept_terms(cx))
440        {
441            cx.notify();
442            return;
443        }
444        let model_registry = LanguageModelRegistry::read_global(cx);
445        let Some(model) = model_registry.active_model() else {
446            return;
447        };
448
449        self.thread.update(cx, |thread, cx| {
450            thread.send_to_model(model, RequestKind::Chat, cx)
451        });
452        cx.notify();
453    }
454
455    fn last_user_message(&self, cx: &Context<Self>) -> Option<MessageId> {
456        self.messages
457            .iter()
458            .rev()
459            .find(|message_id| {
460                self.thread
461                    .read(cx)
462                    .message(**message_id)
463                    .map_or(false, |message| message.role == Role::User)
464            })
465            .cloned()
466    }
467
468    fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
469        self.messages
470            .iter()
471            .position(|id| *id == message_id)
472            .map(|index| &self.messages[index + 1..])
473            .unwrap_or(&[])
474    }
475
476    fn handle_cancel_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
477        self.cancel_editing_message(&menu::Cancel, window, cx);
478    }
479
480    fn handle_regenerate_click(
481        &mut self,
482        _: &ClickEvent,
483        window: &mut Window,
484        cx: &mut Context<Self>,
485    ) {
486        self.confirm_editing_message(&menu::Confirm, window, cx);
487    }
488
489    fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
490        let message_id = self.messages[ix];
491        let Some(message) = self.thread.read(cx).message(message_id) else {
492            return Empty.into_any();
493        };
494
495        let Some(markdown) = self.rendered_messages_by_id.get(&message_id) else {
496            return Empty.into_any();
497        };
498
499        let thread = self.thread.read(cx);
500
501        let context = thread.context_for_message(message_id);
502        let tool_uses = thread.tool_uses_for_message(message_id);
503        let scripting_tool_uses = thread.scripting_tool_uses_for_message(message_id);
504
505        // Don't render user messages that are just there for returning tool results.
506        if message.role == Role::User
507            && (thread.message_has_tool_results(message_id)
508                || thread.message_has_scripting_tool_results(message_id))
509        {
510            return Empty.into_any();
511        }
512
513        let allow_editing_message =
514            message.role == Role::User && self.last_user_message(cx) == Some(message_id);
515
516        let edit_message_editor = self
517            .editing_message
518            .as_ref()
519            .filter(|(id, _)| *id == message_id)
520            .map(|(_, state)| state.editor.clone());
521
522        let colors = cx.theme().colors();
523
524        let message_content = v_flex()
525            .child(
526                if let Some(edit_message_editor) = edit_message_editor.clone() {
527                    div()
528                        .key_context("EditMessageEditor")
529                        .on_action(cx.listener(Self::cancel_editing_message))
530                        .on_action(cx.listener(Self::confirm_editing_message))
531                        .p_2p5()
532                        .child(edit_message_editor)
533                } else {
534                    div().p_2p5().text_ui(cx).child(markdown.clone())
535                },
536            )
537            .when_some(context, |parent, context| {
538                if !context.is_empty() {
539                    parent.child(
540                        h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
541                            context
542                                .into_iter()
543                                .map(|context| ContextPill::added(context, false, false, None)),
544                        ),
545                    )
546                } else {
547                    parent
548                }
549            });
550
551        let styled_message = match message.role {
552            Role::User => v_flex()
553                .id(("message-container", ix))
554                .pt_2p5()
555                .px_2p5()
556                .child(
557                    v_flex()
558                        .bg(colors.editor_background)
559                        .rounded_lg()
560                        .border_1()
561                        .border_color(colors.border)
562                        .shadow_sm()
563                        .child(
564                            h_flex()
565                                .py_1()
566                                .pl_2()
567                                .pr_1()
568                                .bg(colors.editor_foreground.opacity(0.05))
569                                .border_b_1()
570                                .border_color(colors.border)
571                                .justify_between()
572                                .rounded_t(px(6.))
573                                .child(
574                                    h_flex()
575                                        .gap_1p5()
576                                        .child(
577                                            Icon::new(IconName::PersonCircle)
578                                                .size(IconSize::XSmall)
579                                                .color(Color::Muted),
580                                        )
581                                        .child(
582                                            Label::new("You")
583                                                .size(LabelSize::Small)
584                                                .color(Color::Muted),
585                                        ),
586                                )
587                                .when_some(
588                                    edit_message_editor.clone(),
589                                    |this, edit_message_editor| {
590                                        let focus_handle = edit_message_editor.focus_handle(cx);
591                                        this.child(
592                                            h_flex()
593                                                .gap_1()
594                                                .child(
595                                                    Button::new("cancel-edit-message", "Cancel")
596                                                        .label_size(LabelSize::Small)
597                                                        .key_binding(
598                                                            KeyBinding::for_action_in(
599                                                                &menu::Cancel,
600                                                                &focus_handle,
601                                                                window,
602                                                                cx,
603                                                            )
604                                                            .map(|kb| kb.size(rems_from_px(12.))),
605                                                        )
606                                                        .on_click(
607                                                            cx.listener(Self::handle_cancel_click),
608                                                        ),
609                                                )
610                                                .child(
611                                                    Button::new(
612                                                        "confirm-edit-message",
613                                                        "Regenerate",
614                                                    )
615                                                    .label_size(LabelSize::Small)
616                                                    .key_binding(
617                                                        KeyBinding::for_action_in(
618                                                            &menu::Confirm,
619                                                            &focus_handle,
620                                                            window,
621                                                            cx,
622                                                        )
623                                                        .map(|kb| kb.size(rems_from_px(12.))),
624                                                    )
625                                                    .on_click(
626                                                        cx.listener(Self::handle_regenerate_click),
627                                                    ),
628                                                ),
629                                        )
630                                    },
631                                )
632                                .when(
633                                    edit_message_editor.is_none() && allow_editing_message,
634                                    |this| {
635                                        this.child(
636                                            Button::new("edit-message", "Edit")
637                                                .label_size(LabelSize::Small)
638                                                .on_click(cx.listener({
639                                                    let message_text = message.text.clone();
640                                                    move |this, _, window, cx| {
641                                                        this.start_editing_message(
642                                                            message_id,
643                                                            message_text.clone(),
644                                                            window,
645                                                            cx,
646                                                        );
647                                                    }
648                                                })),
649                                        )
650                                    },
651                                ),
652                        )
653                        .child(message_content),
654                ),
655            Role::Assistant => div()
656                .id(("message-container", ix))
657                .child(message_content)
658                .map(|parent| {
659                    if tool_uses.is_empty() && scripting_tool_uses.is_empty() {
660                        return parent;
661                    }
662
663                    parent.child(
664                        v_flex()
665                            .children(
666                                tool_uses
667                                    .into_iter()
668                                    .map(|tool_use| self.render_tool_use(tool_use, cx)),
669                            )
670                            .children(
671                                scripting_tool_uses
672                                    .into_iter()
673                                    .map(|tool_use| self.render_scripting_tool_use(tool_use, cx)),
674                            ),
675                    )
676                }),
677            Role::System => div().id(("message-container", ix)).py_1().px_2().child(
678                v_flex()
679                    .bg(colors.editor_background)
680                    .rounded_sm()
681                    .child(message_content),
682            ),
683        };
684
685        styled_message.into_any()
686    }
687
688    fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement {
689        let is_open = self
690            .expanded_tool_uses
691            .get(&tool_use.id)
692            .copied()
693            .unwrap_or_default();
694
695        div().px_2p5().child(
696            v_flex()
697                .gap_1()
698                .rounded_lg()
699                .border_1()
700                .border_color(cx.theme().colors().border)
701                .child(
702                    h_flex()
703                        .justify_between()
704                        .py_0p5()
705                        .pl_1()
706                        .pr_2()
707                        .bg(cx.theme().colors().editor_foreground.opacity(0.02))
708                        .map(|element| {
709                            if is_open {
710                                element.border_b_1().rounded_t(px(6.))
711                            } else {
712                                element.rounded_md()
713                            }
714                        })
715                        .border_color(cx.theme().colors().border)
716                        .child(
717                            h_flex()
718                                .gap_1()
719                                .child(Disclosure::new("tool-use-disclosure", is_open).on_click(
720                                    cx.listener({
721                                        let tool_use_id = tool_use.id.clone();
722                                        move |this, _event, _window, _cx| {
723                                            let is_open = this
724                                                .expanded_tool_uses
725                                                .entry(tool_use_id.clone())
726                                                .or_insert(false);
727
728                                            *is_open = !*is_open;
729                                        }
730                                    }),
731                                ))
732                                .child(Label::new(tool_use.name)),
733                        )
734                        .child(
735                            Label::new(match tool_use.status {
736                                ToolUseStatus::Pending => "Pending",
737                                ToolUseStatus::Running => "Running",
738                                ToolUseStatus::Finished(_) => "Finished",
739                                ToolUseStatus::Error(_) => "Error",
740                            })
741                            .size(LabelSize::XSmall)
742                            .buffer_font(cx),
743                        ),
744                )
745                .map(|parent| {
746                    if !is_open {
747                        return parent;
748                    }
749
750                    parent.child(
751                        v_flex()
752                            .child(
753                                v_flex()
754                                    .gap_0p5()
755                                    .py_1()
756                                    .px_2p5()
757                                    .border_b_1()
758                                    .border_color(cx.theme().colors().border)
759                                    .child(Label::new("Input:"))
760                                    .child(Label::new(
761                                        serde_json::to_string_pretty(&tool_use.input)
762                                            .unwrap_or_default(),
763                                    )),
764                            )
765                            .map(|parent| match tool_use.status {
766                                ToolUseStatus::Finished(output) => parent.child(
767                                    v_flex()
768                                        .gap_0p5()
769                                        .py_1()
770                                        .px_2p5()
771                                        .child(Label::new("Result:"))
772                                        .child(Label::new(output)),
773                                ),
774                                ToolUseStatus::Error(err) => parent.child(
775                                    v_flex()
776                                        .gap_0p5()
777                                        .py_1()
778                                        .px_2p5()
779                                        .child(Label::new("Error:"))
780                                        .child(Label::new(err)),
781                                ),
782                                ToolUseStatus::Pending | ToolUseStatus::Running => parent,
783                            }),
784                    )
785                }),
786        )
787    }
788
789    fn render_scripting_tool_use(
790        &self,
791        tool_use: ToolUse,
792        cx: &mut Context<Self>,
793    ) -> impl IntoElement {
794        let is_open = self
795            .expanded_tool_uses
796            .get(&tool_use.id)
797            .copied()
798            .unwrap_or_default();
799
800        div().px_2p5().child(
801            v_flex()
802                .gap_1()
803                .rounded_lg()
804                .border_1()
805                .border_color(cx.theme().colors().border)
806                .child(
807                    h_flex()
808                        .justify_between()
809                        .py_0p5()
810                        .pl_1()
811                        .pr_2()
812                        .bg(cx.theme().colors().editor_foreground.opacity(0.02))
813                        .map(|element| {
814                            if is_open {
815                                element.border_b_1().rounded_t(px(6.))
816                            } else {
817                                element.rounded_md()
818                            }
819                        })
820                        .border_color(cx.theme().colors().border)
821                        .child(
822                            h_flex()
823                                .gap_1()
824                                .child(Disclosure::new("tool-use-disclosure", is_open).on_click(
825                                    cx.listener({
826                                        let tool_use_id = tool_use.id.clone();
827                                        move |this, _event, _window, _cx| {
828                                            let is_open = this
829                                                .expanded_tool_uses
830                                                .entry(tool_use_id.clone())
831                                                .or_insert(false);
832
833                                            *is_open = !*is_open;
834                                        }
835                                    }),
836                                ))
837                                .child(Label::new(tool_use.name)),
838                        )
839                        .child(
840                            Label::new(match tool_use.status {
841                                ToolUseStatus::Pending => "Pending",
842                                ToolUseStatus::Running => "Running",
843                                ToolUseStatus::Finished(_) => "Finished",
844                                ToolUseStatus::Error(_) => "Error",
845                            })
846                            .size(LabelSize::XSmall)
847                            .buffer_font(cx),
848                        ),
849                )
850                .map(|parent| {
851                    if !is_open {
852                        return parent;
853                    }
854
855                    let lua_script_markdown =
856                        self.rendered_scripting_tool_uses.get(&tool_use.id).cloned();
857
858                    parent.child(
859                        v_flex()
860                            .child(
861                                v_flex()
862                                    .gap_0p5()
863                                    .py_1()
864                                    .px_2p5()
865                                    .border_b_1()
866                                    .border_color(cx.theme().colors().border)
867                                    .child(Label::new("Input:"))
868                                    .map(|parent| {
869                                        if let Some(markdown) = lua_script_markdown {
870                                            parent.child(markdown)
871                                        } else {
872                                            parent.child(Label::new(
873                                                "Failed to render script input to Markdown",
874                                            ))
875                                        }
876                                    }),
877                            )
878                            .map(|parent| match tool_use.status {
879                                ToolUseStatus::Finished(output) => parent.child(
880                                    v_flex()
881                                        .gap_0p5()
882                                        .py_1()
883                                        .px_2p5()
884                                        .child(Label::new("Result:"))
885                                        .child(Label::new(output)),
886                                ),
887                                ToolUseStatus::Error(err) => parent.child(
888                                    v_flex()
889                                        .gap_0p5()
890                                        .py_1()
891                                        .px_2p5()
892                                        .child(Label::new("Error:"))
893                                        .child(Label::new(err)),
894                                ),
895                                ToolUseStatus::Pending | ToolUseStatus::Running => parent,
896                            }),
897                    )
898                }),
899        )
900    }
901}
902
903impl Render for ActiveThread {
904    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
905        v_flex()
906            .size_full()
907            .child(list(self.list_state.clone()).flex_grow())
908    }
909}