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().p_2p5().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_2p5()
 607                .px_2p5()
 608                .child(
 609                    v_flex()
 610                        .bg(colors.editor_background)
 611                        .rounded_lg()
 612                        .border_1()
 613                        .border_color(colors.border)
 614                        .shadow_sm()
 615                        .child(
 616                            h_flex()
 617                                .py_1()
 618                                .pl_2()
 619                                .pr_1()
 620                                .bg(colors.editor_foreground.opacity(0.05))
 621                                .border_b_1()
 622                                .border_color(colors.border)
 623                                .justify_between()
 624                                .rounded_t(px(6.))
 625                                .child(
 626                                    h_flex()
 627                                        .gap_1p5()
 628                                        .child(
 629                                            Icon::new(IconName::PersonCircle)
 630                                                .size(IconSize::XSmall)
 631                                                .color(Color::Muted),
 632                                        )
 633                                        .child(
 634                                            Label::new("You")
 635                                                .size(LabelSize::Small)
 636                                                .color(Color::Muted),
 637                                        ),
 638                                )
 639                                .when_some(
 640                                    edit_message_editor.clone(),
 641                                    |this, edit_message_editor| {
 642                                        let focus_handle = edit_message_editor.focus_handle(cx);
 643                                        this.child(
 644                                            h_flex()
 645                                                .gap_1()
 646                                                .child(
 647                                                    Button::new("cancel-edit-message", "Cancel")
 648                                                        .label_size(LabelSize::Small)
 649                                                        .key_binding(
 650                                                            KeyBinding::for_action_in(
 651                                                                &menu::Cancel,
 652                                                                &focus_handle,
 653                                                                window,
 654                                                                cx,
 655                                                            )
 656                                                            .map(|kb| kb.size(rems_from_px(12.))),
 657                                                        )
 658                                                        .on_click(
 659                                                            cx.listener(Self::handle_cancel_click),
 660                                                        ),
 661                                                )
 662                                                .child(
 663                                                    Button::new(
 664                                                        "confirm-edit-message",
 665                                                        "Regenerate",
 666                                                    )
 667                                                    .label_size(LabelSize::Small)
 668                                                    .key_binding(
 669                                                        KeyBinding::for_action_in(
 670                                                            &menu::Confirm,
 671                                                            &focus_handle,
 672                                                            window,
 673                                                            cx,
 674                                                        )
 675                                                        .map(|kb| kb.size(rems_from_px(12.))),
 676                                                    )
 677                                                    .on_click(
 678                                                        cx.listener(Self::handle_regenerate_click),
 679                                                    ),
 680                                                ),
 681                                        )
 682                                    },
 683                                )
 684                                .when(
 685                                    edit_message_editor.is_none() && allow_editing_message,
 686                                    |this| {
 687                                        this.child(
 688                                            Button::new("edit-message", "Edit")
 689                                                .label_size(LabelSize::Small)
 690                                                .on_click(cx.listener({
 691                                                    let message_text = message.text.clone();
 692                                                    move |this, _, window, cx| {
 693                                                        this.start_editing_message(
 694                                                            message_id,
 695                                                            message_text.clone(),
 696                                                            window,
 697                                                            cx,
 698                                                        );
 699                                                    }
 700                                                })),
 701                                        )
 702                                    },
 703                                ),
 704                        )
 705                        .child(message_content),
 706                ),
 707            Role::Assistant => {
 708                v_flex()
 709                    .id(("message-container", ix))
 710                    .child(message_content)
 711                    .when(
 712                        !tool_uses.is_empty() || !scripting_tool_uses.is_empty(),
 713                        |parent| {
 714                            parent.child(
 715                                v_flex()
 716                                    .children(
 717                                        tool_uses
 718                                            .into_iter()
 719                                            .map(|tool_use| self.render_tool_use(tool_use, cx)),
 720                                    )
 721                                    .children(scripting_tool_uses.into_iter().map(|tool_use| {
 722                                        self.render_scripting_tool_use(tool_use, cx)
 723                                    })),
 724                            )
 725                        },
 726                    )
 727            }
 728            Role::System => div().id(("message-container", ix)).py_1().px_2().child(
 729                v_flex()
 730                    .bg(colors.editor_background)
 731                    .rounded_sm()
 732                    .child(message_content),
 733            ),
 734        };
 735
 736        styled_message.into_any()
 737    }
 738
 739    fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement {
 740        let is_open = self
 741            .expanded_tool_uses
 742            .get(&tool_use.id)
 743            .copied()
 744            .unwrap_or_default();
 745
 746        let lighter_border = cx.theme().colors().border.opacity(0.5);
 747
 748        div().px_2p5().child(
 749            v_flex()
 750                .rounded_lg()
 751                .border_1()
 752                .border_color(lighter_border)
 753                .child(
 754                    h_flex()
 755                        .justify_between()
 756                        .py_1()
 757                        .pl_1()
 758                        .pr_2()
 759                        .bg(cx.theme().colors().editor_foreground.opacity(0.025))
 760                        .map(|element| {
 761                            if is_open {
 762                                element.border_b_1().rounded_t_md()
 763                            } else {
 764                                element.rounded_md()
 765                            }
 766                        })
 767                        .border_color(lighter_border)
 768                        .child(
 769                            h_flex()
 770                                .gap_1()
 771                                .child(Disclosure::new("tool-use-disclosure", is_open).on_click(
 772                                    cx.listener({
 773                                        let tool_use_id = tool_use.id.clone();
 774                                        move |this, _event, _window, _cx| {
 775                                            let is_open = this
 776                                                .expanded_tool_uses
 777                                                .entry(tool_use_id.clone())
 778                                                .or_insert(false);
 779
 780                                            *is_open = !*is_open;
 781                                        }
 782                                    }),
 783                                ))
 784                                .child(
 785                                    Label::new(tool_use.name)
 786                                        .size(LabelSize::Small)
 787                                        .buffer_font(cx),
 788                                ),
 789                        )
 790                        .child({
 791                            let (icon_name, color, animated) = match &tool_use.status {
 792                                ToolUseStatus::Pending => {
 793                                    (IconName::Warning, Color::Warning, false)
 794                                }
 795                                ToolUseStatus::Running => {
 796                                    (IconName::ArrowCircle, Color::Accent, true)
 797                                }
 798                                ToolUseStatus::Finished(_) => {
 799                                    (IconName::Check, Color::Success, false)
 800                                }
 801                                ToolUseStatus::Error(_) => (IconName::Close, Color::Error, false),
 802                            };
 803
 804                            let icon = Icon::new(icon_name).color(color).size(IconSize::Small);
 805
 806                            if animated {
 807                                icon.with_animation(
 808                                    "arrow-circle",
 809                                    Animation::new(Duration::from_secs(2)).repeat(),
 810                                    |icon, delta| {
 811                                        icon.transform(Transformation::rotate(percentage(delta)))
 812                                    },
 813                                )
 814                                .into_any_element()
 815                            } else {
 816                                icon.into_any_element()
 817                            }
 818                        }),
 819                )
 820                .map(|parent| {
 821                    if !is_open {
 822                        return parent;
 823                    }
 824
 825                    let content_container = || v_flex().py_1().gap_0p5().px_2p5();
 826
 827                    parent.child(
 828                        v_flex()
 829                            .gap_1()
 830                            .bg(cx.theme().colors().editor_background)
 831                            .rounded_b_lg()
 832                            .child(
 833                                content_container()
 834                                    .border_b_1()
 835                                    .border_color(lighter_border)
 836                                    .child(
 837                                        Label::new("Input")
 838                                            .size(LabelSize::XSmall)
 839                                            .color(Color::Muted)
 840                                            .buffer_font(cx),
 841                                    )
 842                                    .child(
 843                                        Label::new(
 844                                            serde_json::to_string_pretty(&tool_use.input)
 845                                                .unwrap_or_default(),
 846                                        )
 847                                        .size(LabelSize::Small)
 848                                        .buffer_font(cx),
 849                                    ),
 850                            )
 851                            .map(|container| match tool_use.status {
 852                                ToolUseStatus::Finished(output) => container.child(
 853                                    content_container()
 854                                        .child(
 855                                            Label::new("Result")
 856                                                .size(LabelSize::XSmall)
 857                                                .color(Color::Muted)
 858                                                .buffer_font(cx),
 859                                        )
 860                                        .child(
 861                                            Label::new(output)
 862                                                .size(LabelSize::Small)
 863                                                .buffer_font(cx),
 864                                        ),
 865                                ),
 866                                ToolUseStatus::Running => container.child(
 867                                    content_container().child(
 868                                        h_flex()
 869                                            .gap_1()
 870                                            .pb_1()
 871                                            .child(
 872                                                Icon::new(IconName::ArrowCircle)
 873                                                    .size(IconSize::Small)
 874                                                    .color(Color::Accent)
 875                                                    .with_animation(
 876                                                        "arrow-circle",
 877                                                        Animation::new(Duration::from_secs(2))
 878                                                            .repeat(),
 879                                                        |icon, delta| {
 880                                                            icon.transform(Transformation::rotate(
 881                                                                percentage(delta),
 882                                                            ))
 883                                                        },
 884                                                    ),
 885                                            )
 886                                            .child(
 887                                                Label::new("Running…")
 888                                                    .size(LabelSize::XSmall)
 889                                                    .color(Color::Muted)
 890                                                    .buffer_font(cx),
 891                                            ),
 892                                    ),
 893                                ),
 894                                ToolUseStatus::Error(err) => container.child(
 895                                    content_container()
 896                                        .child(
 897                                            Label::new("Error")
 898                                                .size(LabelSize::XSmall)
 899                                                .color(Color::Muted)
 900                                                .buffer_font(cx),
 901                                        )
 902                                        .child(
 903                                            Label::new(err).size(LabelSize::Small).buffer_font(cx),
 904                                        ),
 905                                ),
 906                                ToolUseStatus::Pending => container,
 907                            }),
 908                    )
 909                }),
 910        )
 911    }
 912
 913    fn render_scripting_tool_use(
 914        &self,
 915        tool_use: ToolUse,
 916        cx: &mut Context<Self>,
 917    ) -> impl IntoElement {
 918        let is_open = self
 919            .expanded_tool_uses
 920            .get(&tool_use.id)
 921            .copied()
 922            .unwrap_or_default();
 923
 924        div().px_2p5().child(
 925            v_flex()
 926                .gap_1()
 927                .rounded_lg()
 928                .border_1()
 929                .border_color(cx.theme().colors().border)
 930                .child(
 931                    h_flex()
 932                        .justify_between()
 933                        .py_0p5()
 934                        .pl_1()
 935                        .pr_2()
 936                        .bg(cx.theme().colors().editor_foreground.opacity(0.02))
 937                        .map(|element| {
 938                            if is_open {
 939                                element.border_b_1().rounded_t_md()
 940                            } else {
 941                                element.rounded_md()
 942                            }
 943                        })
 944                        .border_color(cx.theme().colors().border)
 945                        .child(
 946                            h_flex()
 947                                .gap_1()
 948                                .child(Disclosure::new("tool-use-disclosure", is_open).on_click(
 949                                    cx.listener({
 950                                        let tool_use_id = tool_use.id.clone();
 951                                        move |this, _event, _window, _cx| {
 952                                            let is_open = this
 953                                                .expanded_tool_uses
 954                                                .entry(tool_use_id.clone())
 955                                                .or_insert(false);
 956
 957                                            *is_open = !*is_open;
 958                                        }
 959                                    }),
 960                                ))
 961                                .child(Label::new(tool_use.name)),
 962                        )
 963                        .child(
 964                            Label::new(match tool_use.status {
 965                                ToolUseStatus::Pending => "Pending",
 966                                ToolUseStatus::Running => "Running",
 967                                ToolUseStatus::Finished(_) => "Finished",
 968                                ToolUseStatus::Error(_) => "Error",
 969                            })
 970                            .size(LabelSize::XSmall)
 971                            .buffer_font(cx),
 972                        ),
 973                )
 974                .map(|parent| {
 975                    if !is_open {
 976                        return parent;
 977                    }
 978
 979                    let lua_script_markdown =
 980                        self.rendered_scripting_tool_uses.get(&tool_use.id).cloned();
 981
 982                    parent.child(
 983                        v_flex()
 984                            .child(
 985                                v_flex()
 986                                    .gap_0p5()
 987                                    .py_1()
 988                                    .px_2p5()
 989                                    .border_b_1()
 990                                    .border_color(cx.theme().colors().border)
 991                                    .child(Label::new("Input:"))
 992                                    .map(|parent| {
 993                                        if let Some(markdown) = lua_script_markdown {
 994                                            parent.child(markdown)
 995                                        } else {
 996                                            parent.child(Label::new(
 997                                                "Failed to render script input to Markdown",
 998                                            ))
 999                                        }
1000                                    }),
1001                            )
1002                            .map(|parent| match tool_use.status {
1003                                ToolUseStatus::Finished(output) => parent.child(
1004                                    v_flex()
1005                                        .gap_0p5()
1006                                        .py_1()
1007                                        .px_2p5()
1008                                        .child(Label::new("Result:"))
1009                                        .child(Label::new(output)),
1010                                ),
1011                                ToolUseStatus::Error(err) => parent.child(
1012                                    v_flex()
1013                                        .gap_0p5()
1014                                        .py_1()
1015                                        .px_2p5()
1016                                        .child(Label::new("Error:"))
1017                                        .child(Label::new(err)),
1018                                ),
1019                                ToolUseStatus::Pending | ToolUseStatus::Running => parent,
1020                            }),
1021                    )
1022                }),
1023        )
1024    }
1025}
1026
1027impl Render for ActiveThread {
1028    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
1029        v_flex()
1030            .size_full()
1031            .child(list(self.list_state.clone()).flex_grow())
1032    }
1033}