active_thread.rs

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