active_thread.rs

   1use crate::AssistantPanel;
   2use crate::context::{AssistantContext, ContextId};
   3use crate::context_picker::MentionLink;
   4use crate::thread::{
   5    LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError,
   6    ThreadEvent, ThreadFeedback,
   7};
   8use crate::thread_store::ThreadStore;
   9use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus};
  10use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill};
  11use anyhow::Context as _;
  12use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting};
  13use collections::HashMap;
  14use editor::scroll::Autoscroll;
  15use editor::{Editor, MultiBuffer};
  16use gpui::{
  17    AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, DefiniteLength,
  18    EdgesRefinement, Empty, Entity, Focusable, Hsla, Length, ListAlignment, ListState, MouseButton,
  19    PlatformDisplay, ScrollHandle, Stateful, StyleRefinement, Subscription, Task,
  20    TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle,
  21    linear_color_stop, linear_gradient, list, percentage, pulsating_between,
  22};
  23use language::{Buffer, LanguageRegistry};
  24use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role};
  25use markdown::{Markdown, MarkdownElement, MarkdownStyle};
  26use project::ProjectItem as _;
  27use settings::{Settings as _, update_settings_file};
  28use std::rc::Rc;
  29use std::sync::Arc;
  30use std::time::Duration;
  31use text::ToPoint;
  32use theme::ThemeSettings;
  33use ui::{Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*};
  34use util::ResultExt as _;
  35use workspace::{OpenOptions, Workspace};
  36
  37use crate::context_store::ContextStore;
  38
  39pub struct ActiveThread {
  40    language_registry: Arc<LanguageRegistry>,
  41    thread_store: Entity<ThreadStore>,
  42    thread: Entity<Thread>,
  43    context_store: Entity<ContextStore>,
  44    workspace: WeakEntity<Workspace>,
  45    save_thread_task: Option<Task<()>>,
  46    messages: Vec<MessageId>,
  47    list_state: ListState,
  48    scrollbar_state: ScrollbarState,
  49    show_scrollbar: bool,
  50    hide_scrollbar_task: Option<Task<()>>,
  51    rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
  52    rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
  53    editing_message: Option<(MessageId, EditMessageState)>,
  54    expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
  55    expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
  56    last_error: Option<ThreadError>,
  57    notifications: Vec<WindowHandle<AgentNotification>>,
  58    _subscriptions: Vec<Subscription>,
  59    notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
  60    feedback_message_editor: Option<Entity<Editor>>,
  61}
  62
  63struct RenderedMessage {
  64    language_registry: Arc<LanguageRegistry>,
  65    segments: Vec<RenderedMessageSegment>,
  66}
  67
  68#[derive(Clone)]
  69struct RenderedToolUse {
  70    label: Entity<Markdown>,
  71    input: Entity<Markdown>,
  72    output: Entity<Markdown>,
  73}
  74
  75impl RenderedMessage {
  76    fn from_segments(
  77        segments: &[MessageSegment],
  78        language_registry: Arc<LanguageRegistry>,
  79        cx: &mut App,
  80    ) -> Self {
  81        let mut this = Self {
  82            language_registry,
  83            segments: Vec::with_capacity(segments.len()),
  84        };
  85        for segment in segments {
  86            this.push_segment(segment, cx);
  87        }
  88        this
  89    }
  90
  91    fn append_thinking(&mut self, text: &String, cx: &mut App) {
  92        if let Some(RenderedMessageSegment::Thinking {
  93            content,
  94            scroll_handle,
  95        }) = self.segments.last_mut()
  96        {
  97            content.update(cx, |markdown, cx| {
  98                markdown.append(text, cx);
  99            });
 100            scroll_handle.scroll_to_bottom();
 101        } else {
 102            self.segments.push(RenderedMessageSegment::Thinking {
 103                content: render_markdown(text.into(), self.language_registry.clone(), cx),
 104                scroll_handle: ScrollHandle::default(),
 105            });
 106        }
 107    }
 108
 109    fn append_text(&mut self, text: &String, cx: &mut App) {
 110        if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() {
 111            markdown.update(cx, |markdown, cx| markdown.append(text, cx));
 112        } else {
 113            self.segments
 114                .push(RenderedMessageSegment::Text(render_markdown(
 115                    SharedString::from(text),
 116                    self.language_registry.clone(),
 117                    cx,
 118                )));
 119        }
 120    }
 121
 122    fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) {
 123        let rendered_segment = match segment {
 124            MessageSegment::Thinking(text) => RenderedMessageSegment::Thinking {
 125                content: render_markdown(text.into(), self.language_registry.clone(), cx),
 126                scroll_handle: ScrollHandle::default(),
 127            },
 128            MessageSegment::Text(text) => RenderedMessageSegment::Text(render_markdown(
 129                text.into(),
 130                self.language_registry.clone(),
 131                cx,
 132            )),
 133        };
 134        self.segments.push(rendered_segment);
 135    }
 136}
 137
 138enum RenderedMessageSegment {
 139    Thinking {
 140        content: Entity<Markdown>,
 141        scroll_handle: ScrollHandle,
 142    },
 143    Text(Entity<Markdown>),
 144}
 145
 146fn render_markdown(
 147    text: SharedString,
 148    language_registry: Arc<LanguageRegistry>,
 149    cx: &mut App,
 150) -> Entity<Markdown> {
 151    cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))
 152}
 153
 154fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
 155    let theme_settings = ThemeSettings::get_global(cx);
 156    let colors = cx.theme().colors();
 157    let ui_font_size = TextSize::Default.rems(cx);
 158    let buffer_font_size = TextSize::Small.rems(cx);
 159    let mut text_style = window.text_style();
 160
 161    text_style.refine(&TextStyleRefinement {
 162        font_family: Some(theme_settings.ui_font.family.clone()),
 163        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
 164        font_features: Some(theme_settings.ui_font.features.clone()),
 165        font_size: Some(ui_font_size.into()),
 166        color: Some(cx.theme().colors().text),
 167        ..Default::default()
 168    });
 169
 170    MarkdownStyle {
 171        base_text_style: text_style,
 172        syntax: cx.theme().syntax().clone(),
 173        selection_background_color: cx.theme().players().local().selection,
 174        code_block_overflow_x_scroll: true,
 175        table_overflow_x_scroll: true,
 176        code_block: StyleRefinement {
 177            margin: EdgesRefinement {
 178                top: Some(Length::Definite(rems(0.).into())),
 179                left: Some(Length::Definite(rems(0.).into())),
 180                right: Some(Length::Definite(rems(0.).into())),
 181                bottom: Some(Length::Definite(rems(0.5).into())),
 182            },
 183            padding: EdgesRefinement {
 184                top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 185                left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 186                right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 187                bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 188            },
 189            background: Some(colors.editor_background.into()),
 190            border_color: Some(colors.border_variant),
 191            border_widths: EdgesRefinement {
 192                top: Some(AbsoluteLength::Pixels(Pixels(1.))),
 193                left: Some(AbsoluteLength::Pixels(Pixels(1.))),
 194                right: Some(AbsoluteLength::Pixels(Pixels(1.))),
 195                bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
 196            },
 197            text: Some(TextStyleRefinement {
 198                font_family: Some(theme_settings.buffer_font.family.clone()),
 199                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 200                font_features: Some(theme_settings.buffer_font.features.clone()),
 201                font_size: Some(buffer_font_size.into()),
 202                ..Default::default()
 203            }),
 204            ..Default::default()
 205        },
 206        inline_code: TextStyleRefinement {
 207            font_family: Some(theme_settings.buffer_font.family.clone()),
 208            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 209            font_features: Some(theme_settings.buffer_font.features.clone()),
 210            font_size: Some(buffer_font_size.into()),
 211            background_color: Some(colors.editor_foreground.opacity(0.08)),
 212            ..Default::default()
 213        },
 214        link: TextStyleRefinement {
 215            background_color: Some(colors.editor_foreground.opacity(0.025)),
 216            underline: Some(UnderlineStyle {
 217                color: Some(colors.text_accent.opacity(0.5)),
 218                thickness: px(1.),
 219                ..Default::default()
 220            }),
 221            ..Default::default()
 222        },
 223        link_callback: Some(Rc::new(move |url, cx| {
 224            if MentionLink::is_valid(url) {
 225                let colors = cx.theme().colors();
 226                Some(TextStyleRefinement {
 227                    background_color: Some(colors.element_background),
 228                    ..Default::default()
 229                })
 230            } else {
 231                None
 232            }
 233        })),
 234        ..Default::default()
 235    }
 236}
 237
 238fn render_tool_use_markdown(
 239    text: SharedString,
 240    language_registry: Arc<LanguageRegistry>,
 241    cx: &mut App,
 242) -> Entity<Markdown> {
 243    cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))
 244}
 245
 246fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
 247    let theme_settings = ThemeSettings::get_global(cx);
 248    let colors = cx.theme().colors();
 249    let ui_font_size = TextSize::Default.rems(cx);
 250    let buffer_font_size = TextSize::Small.rems(cx);
 251    let mut text_style = window.text_style();
 252
 253    text_style.refine(&TextStyleRefinement {
 254        font_family: Some(theme_settings.ui_font.family.clone()),
 255        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
 256        font_features: Some(theme_settings.ui_font.features.clone()),
 257        font_size: Some(ui_font_size.into()),
 258        color: Some(cx.theme().colors().text),
 259        ..Default::default()
 260    });
 261
 262    MarkdownStyle {
 263        base_text_style: text_style,
 264        syntax: cx.theme().syntax().clone(),
 265        selection_background_color: cx.theme().players().local().selection,
 266        code_block_overflow_x_scroll: true,
 267        code_block: StyleRefinement {
 268            margin: EdgesRefinement::default(),
 269            padding: EdgesRefinement::default(),
 270            background: Some(colors.editor_background.into()),
 271            border_color: None,
 272            border_widths: EdgesRefinement::default(),
 273            text: Some(TextStyleRefinement {
 274                font_family: Some(theme_settings.buffer_font.family.clone()),
 275                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 276                font_features: Some(theme_settings.buffer_font.features.clone()),
 277                font_size: Some(buffer_font_size.into()),
 278                ..Default::default()
 279            }),
 280            ..Default::default()
 281        },
 282        inline_code: TextStyleRefinement {
 283            font_family: Some(theme_settings.buffer_font.family.clone()),
 284            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 285            font_features: Some(theme_settings.buffer_font.features.clone()),
 286            font_size: Some(TextSize::XSmall.rems(cx).into()),
 287            ..Default::default()
 288        },
 289        heading: StyleRefinement {
 290            text: Some(TextStyleRefinement {
 291                font_size: Some(ui_font_size.into()),
 292                ..Default::default()
 293            }),
 294            ..Default::default()
 295        },
 296        ..Default::default()
 297    }
 298}
 299
 300fn open_markdown_link(
 301    text: SharedString,
 302    workspace: WeakEntity<Workspace>,
 303    window: &mut Window,
 304    cx: &mut App,
 305) {
 306    let Some(workspace) = workspace.upgrade() else {
 307        cx.open_url(&text);
 308        return;
 309    };
 310
 311    match MentionLink::try_parse(&text, &workspace, cx) {
 312        Some(MentionLink::File(path, entry)) => workspace.update(cx, |workspace, cx| {
 313            if entry.is_dir() {
 314                workspace.project().update(cx, |_, cx| {
 315                    cx.emit(project::Event::RevealInProjectPanel(entry.id));
 316                })
 317            } else {
 318                workspace
 319                    .open_path(path, None, true, window, cx)
 320                    .detach_and_log_err(cx);
 321            }
 322        }),
 323        Some(MentionLink::Symbol(path, symbol_name)) => {
 324            let open_task = workspace.update(cx, |workspace, cx| {
 325                workspace.open_path(path, None, true, window, cx)
 326            });
 327            window
 328                .spawn(cx, async move |cx| {
 329                    let active_editor = open_task
 330                        .await?
 331                        .downcast::<Editor>()
 332                        .context("Item is not an editor")?;
 333                    active_editor.update_in(cx, |editor, window, cx| {
 334                        let symbol_range = editor
 335                            .buffer()
 336                            .read(cx)
 337                            .snapshot(cx)
 338                            .outline(None)
 339                            .and_then(|outline| {
 340                                outline
 341                                    .find_most_similar(&symbol_name)
 342                                    .map(|(_, item)| item.range.clone())
 343                            })
 344                            .context("Could not find matching symbol")?;
 345
 346                        editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
 347                            s.select_anchor_ranges([symbol_range.start..symbol_range.start])
 348                        });
 349                        anyhow::Ok(())
 350                    })
 351                })
 352                .detach_and_log_err(cx);
 353        }
 354        Some(MentionLink::Thread(thread_id)) => workspace.update(cx, |workspace, cx| {
 355            if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 356                panel.update(cx, |panel, cx| {
 357                    panel
 358                        .open_thread(&thread_id, window, cx)
 359                        .detach_and_log_err(cx)
 360                });
 361            }
 362        }),
 363        Some(MentionLink::Fetch(url)) => cx.open_url(&url),
 364        None => cx.open_url(&text),
 365    }
 366}
 367
 368struct EditMessageState {
 369    editor: Entity<Editor>,
 370}
 371
 372impl ActiveThread {
 373    pub fn new(
 374        thread: Entity<Thread>,
 375        thread_store: Entity<ThreadStore>,
 376        language_registry: Arc<LanguageRegistry>,
 377        context_store: Entity<ContextStore>,
 378        workspace: WeakEntity<Workspace>,
 379        window: &mut Window,
 380        cx: &mut Context<Self>,
 381    ) -> Self {
 382        let subscriptions = vec![
 383            cx.observe(&thread, |_, _, cx| cx.notify()),
 384            cx.subscribe_in(&thread, window, Self::handle_thread_event),
 385        ];
 386
 387        let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.), {
 388            let this = cx.entity().downgrade();
 389            move |ix, window: &mut Window, cx: &mut App| {
 390                this.update(cx, |this, cx| this.render_message(ix, window, cx))
 391                    .unwrap()
 392            }
 393        });
 394
 395        let mut this = Self {
 396            language_registry,
 397            thread_store,
 398            thread: thread.clone(),
 399            context_store,
 400            workspace,
 401            save_thread_task: None,
 402            messages: Vec::new(),
 403            rendered_messages_by_id: HashMap::default(),
 404            rendered_tool_uses: HashMap::default(),
 405            expanded_tool_uses: HashMap::default(),
 406            expanded_thinking_segments: HashMap::default(),
 407            list_state: list_state.clone(),
 408            scrollbar_state: ScrollbarState::new(list_state),
 409            show_scrollbar: false,
 410            hide_scrollbar_task: None,
 411            editing_message: None,
 412            last_error: None,
 413            notifications: Vec::new(),
 414            _subscriptions: subscriptions,
 415            notification_subscriptions: HashMap::default(),
 416            feedback_message_editor: None,
 417        };
 418
 419        for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
 420            this.push_message(&message.id, &message.segments, window, cx);
 421
 422            for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) {
 423                this.render_tool_use_markdown(
 424                    tool_use.id.clone(),
 425                    tool_use.ui_text.clone(),
 426                    &tool_use.input,
 427                    tool_use.status.text(),
 428                    cx,
 429                );
 430            }
 431        }
 432
 433        this
 434    }
 435
 436    pub fn thread(&self) -> &Entity<Thread> {
 437        &self.thread
 438    }
 439
 440    pub fn is_empty(&self) -> bool {
 441        self.messages.is_empty()
 442    }
 443
 444    pub fn summary(&self, cx: &App) -> Option<SharedString> {
 445        self.thread.read(cx).summary()
 446    }
 447
 448    pub fn summary_or_default(&self, cx: &App) -> SharedString {
 449        self.thread.read(cx).summary_or_default()
 450    }
 451
 452    pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
 453        self.last_error.take();
 454        self.thread
 455            .update(cx, |thread, cx| thread.cancel_last_completion(cx))
 456    }
 457
 458    pub fn last_error(&self) -> Option<ThreadError> {
 459        self.last_error.clone()
 460    }
 461
 462    pub fn clear_last_error(&mut self) {
 463        self.last_error.take();
 464    }
 465
 466    fn push_message(
 467        &mut self,
 468        id: &MessageId,
 469        segments: &[MessageSegment],
 470        _window: &mut Window,
 471        cx: &mut Context<Self>,
 472    ) {
 473        let old_len = self.messages.len();
 474        self.messages.push(*id);
 475        self.list_state.splice(old_len..old_len, 1);
 476
 477        let rendered_message =
 478            RenderedMessage::from_segments(segments, self.language_registry.clone(), cx);
 479        self.rendered_messages_by_id.insert(*id, rendered_message);
 480    }
 481
 482    fn edited_message(
 483        &mut self,
 484        id: &MessageId,
 485        segments: &[MessageSegment],
 486        _window: &mut Window,
 487        cx: &mut Context<Self>,
 488    ) {
 489        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
 490            return;
 491        };
 492        self.list_state.splice(index..index + 1, 1);
 493        let rendered_message =
 494            RenderedMessage::from_segments(segments, self.language_registry.clone(), cx);
 495        self.rendered_messages_by_id.insert(*id, rendered_message);
 496    }
 497
 498    fn deleted_message(&mut self, id: &MessageId) {
 499        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
 500            return;
 501        };
 502        self.messages.remove(index);
 503        self.list_state.splice(index..index + 1, 0);
 504        self.rendered_messages_by_id.remove(id);
 505    }
 506
 507    fn render_tool_use_markdown(
 508        &mut self,
 509        tool_use_id: LanguageModelToolUseId,
 510        tool_label: impl Into<SharedString>,
 511        tool_input: &serde_json::Value,
 512        tool_output: SharedString,
 513        cx: &mut Context<Self>,
 514    ) {
 515        let rendered = RenderedToolUse {
 516            label: render_tool_use_markdown(tool_label.into(), self.language_registry.clone(), cx),
 517            input: render_tool_use_markdown(
 518                format!(
 519                    "```json\n{}\n```",
 520                    serde_json::to_string_pretty(tool_input).unwrap_or_default()
 521                )
 522                .into(),
 523                self.language_registry.clone(),
 524                cx,
 525            ),
 526            output: render_tool_use_markdown(tool_output, self.language_registry.clone(), cx),
 527        };
 528        self.rendered_tool_uses
 529            .insert(tool_use_id.clone(), rendered);
 530    }
 531
 532    fn handle_thread_event(
 533        &mut self,
 534        _thread: &Entity<Thread>,
 535        event: &ThreadEvent,
 536        window: &mut Window,
 537        cx: &mut Context<Self>,
 538    ) {
 539        match event {
 540            ThreadEvent::ShowError(error) => {
 541                self.last_error = Some(error.clone());
 542            }
 543            ThreadEvent::StreamedCompletion
 544            | ThreadEvent::SummaryGenerated
 545            | ThreadEvent::SummaryChanged => {
 546                self.save_thread(cx);
 547            }
 548            ThreadEvent::DoneStreaming => {
 549                let thread = self.thread.read(cx);
 550
 551                if !thread.is_generating() {
 552                    self.show_notification(
 553                        if thread.used_tools_since_last_user_message() {
 554                            "Finished running tools"
 555                        } else {
 556                            "New message"
 557                        },
 558                        IconName::ZedAssistant,
 559                        window,
 560                        cx,
 561                    );
 562                }
 563            }
 564            ThreadEvent::ToolConfirmationNeeded => {
 565                self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
 566            }
 567            ThreadEvent::StreamedAssistantText(message_id, text) => {
 568                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
 569                    rendered_message.append_text(text, cx);
 570                }
 571            }
 572            ThreadEvent::StreamedAssistantThinking(message_id, text) => {
 573                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
 574                    rendered_message.append_thinking(text, cx);
 575                }
 576            }
 577            ThreadEvent::MessageAdded(message_id) => {
 578                if let Some(message_segments) = self
 579                    .thread
 580                    .read(cx)
 581                    .message(*message_id)
 582                    .map(|message| message.segments.clone())
 583                {
 584                    self.push_message(message_id, &message_segments, window, cx);
 585                }
 586
 587                self.save_thread(cx);
 588                cx.notify();
 589            }
 590            ThreadEvent::MessageEdited(message_id) => {
 591                if let Some(message_segments) = self
 592                    .thread
 593                    .read(cx)
 594                    .message(*message_id)
 595                    .map(|message| message.segments.clone())
 596                {
 597                    self.edited_message(message_id, &message_segments, window, cx);
 598                }
 599
 600                self.save_thread(cx);
 601                cx.notify();
 602            }
 603            ThreadEvent::MessageDeleted(message_id) => {
 604                self.deleted_message(message_id);
 605                self.save_thread(cx);
 606                cx.notify();
 607            }
 608            ThreadEvent::UsePendingTools => {
 609                let tool_uses = self
 610                    .thread
 611                    .update(cx, |thread, cx| thread.use_pending_tools(cx));
 612
 613                for tool_use in tool_uses {
 614                    self.render_tool_use_markdown(
 615                        tool_use.id.clone(),
 616                        tool_use.ui_text.clone(),
 617                        &tool_use.input,
 618                        "".into(),
 619                        cx,
 620                    );
 621                }
 622            }
 623            ThreadEvent::ToolFinished {
 624                pending_tool_use,
 625                canceled,
 626                ..
 627            } => {
 628                let canceled = *canceled;
 629                if let Some(tool_use) = pending_tool_use {
 630                    self.render_tool_use_markdown(
 631                        tool_use.id.clone(),
 632                        tool_use.ui_text.clone(),
 633                        &tool_use.input,
 634                        self.thread
 635                            .read(cx)
 636                            .tool_result(&tool_use.id)
 637                            .map(|result| result.content.clone().into())
 638                            .unwrap_or("".into()),
 639                        cx,
 640                    );
 641                }
 642
 643                if self.thread.read(cx).all_tools_finished() {
 644                    let model_registry = LanguageModelRegistry::read_global(cx);
 645                    if let Some(ConfiguredModel { model, .. }) = model_registry.default_model() {
 646                        self.thread.update(cx, |thread, cx| {
 647                            thread.attach_tool_results(cx);
 648                            if !canceled {
 649                                thread.send_to_model(model, RequestKind::Chat, cx);
 650                            }
 651                        });
 652                    }
 653                }
 654            }
 655            ThreadEvent::CheckpointChanged => cx.notify(),
 656        }
 657    }
 658
 659    fn show_notification(
 660        &mut self,
 661        caption: impl Into<SharedString>,
 662        icon: IconName,
 663        window: &mut Window,
 664        cx: &mut Context<ActiveThread>,
 665    ) {
 666        if window.is_window_active() || !self.notifications.is_empty() {
 667            return;
 668        }
 669
 670        let title = self
 671            .thread
 672            .read(cx)
 673            .summary()
 674            .unwrap_or("Agent Panel".into());
 675
 676        match AssistantSettings::get_global(cx).notify_when_agent_waiting {
 677            NotifyWhenAgentWaiting::PrimaryScreen => {
 678                if let Some(primary) = cx.primary_display() {
 679                    self.pop_up(icon, caption.into(), title.clone(), window, primary, cx);
 680                }
 681            }
 682            NotifyWhenAgentWaiting::AllScreens => {
 683                let caption = caption.into();
 684                for screen in cx.displays() {
 685                    self.pop_up(icon, caption.clone(), title.clone(), window, screen, cx);
 686                }
 687            }
 688            NotifyWhenAgentWaiting::Never => {
 689                // Don't show anything
 690            }
 691        }
 692    }
 693
 694    fn pop_up(
 695        &mut self,
 696        icon: IconName,
 697        caption: SharedString,
 698        title: SharedString,
 699        window: &mut Window,
 700        screen: Rc<dyn PlatformDisplay>,
 701        cx: &mut Context<'_, ActiveThread>,
 702    ) {
 703        let options = AgentNotification::window_options(screen, cx);
 704
 705        if let Some(screen_window) = cx
 706            .open_window(options, |_, cx| {
 707                cx.new(|_| AgentNotification::new(title.clone(), caption.clone(), icon))
 708            })
 709            .log_err()
 710        {
 711            if let Some(pop_up) = screen_window.entity(cx).log_err() {
 712                self.notification_subscriptions
 713                    .entry(screen_window)
 714                    .or_insert_with(Vec::new)
 715                    .push(cx.subscribe_in(&pop_up, window, {
 716                        |this, _, event, window, cx| match event {
 717                            AgentNotificationEvent::Accepted => {
 718                                let handle = window.window_handle();
 719                                cx.activate(true); // Switch back to the Zed application
 720
 721                                let workspace_handle = this.workspace.clone();
 722
 723                                // If there are multiple Zed windows, activate the correct one.
 724                                cx.defer(move |cx| {
 725                                    handle
 726                                        .update(cx, |_view, window, _cx| {
 727                                            window.activate_window();
 728
 729                                            if let Some(workspace) = workspace_handle.upgrade() {
 730                                                workspace.update(_cx, |workspace, cx| {
 731                                                    workspace
 732                                                        .focus_panel::<AssistantPanel>(window, cx);
 733                                                });
 734                                            }
 735                                        })
 736                                        .log_err();
 737                                });
 738
 739                                this.dismiss_notifications(cx);
 740                            }
 741                            AgentNotificationEvent::Dismissed => {
 742                                this.dismiss_notifications(cx);
 743                            }
 744                        }
 745                    }));
 746
 747                self.notifications.push(screen_window);
 748
 749                // If the user manually refocuses the original window, dismiss the popup.
 750                self.notification_subscriptions
 751                    .entry(screen_window)
 752                    .or_insert_with(Vec::new)
 753                    .push({
 754                        let pop_up_weak = pop_up.downgrade();
 755
 756                        cx.observe_window_activation(window, move |_, window, cx| {
 757                            if window.is_window_active() {
 758                                if let Some(pop_up) = pop_up_weak.upgrade() {
 759                                    pop_up.update(cx, |_, cx| {
 760                                        cx.emit(AgentNotificationEvent::Dismissed);
 761                                    });
 762                                }
 763                            }
 764                        })
 765                    });
 766            }
 767        }
 768    }
 769
 770    /// Spawns a task to save the active thread.
 771    ///
 772    /// Only one task to save the thread will be in flight at a time.
 773    fn save_thread(&mut self, cx: &mut Context<Self>) {
 774        let thread = self.thread.clone();
 775        self.save_thread_task = Some(cx.spawn(async move |this, cx| {
 776            let task = this
 777                .update(cx, |this, cx| {
 778                    this.thread_store
 779                        .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx))
 780                })
 781                .ok();
 782
 783            if let Some(task) = task {
 784                task.await.log_err();
 785            }
 786        }));
 787    }
 788
 789    fn start_editing_message(
 790        &mut self,
 791        message_id: MessageId,
 792        message_segments: &[MessageSegment],
 793        window: &mut Window,
 794        cx: &mut Context<Self>,
 795    ) {
 796        // User message should always consist of a single text segment,
 797        // therefore we can skip returning early if it's not a text segment.
 798        let Some(MessageSegment::Text(message_text)) = message_segments.first() else {
 799            return;
 800        };
 801
 802        let buffer = cx.new(|cx| {
 803            MultiBuffer::singleton(cx.new(|cx| Buffer::local(message_text.clone(), cx)), cx)
 804        });
 805        let editor = cx.new(|cx| {
 806            let mut editor = Editor::new(
 807                editor::EditorMode::AutoHeight { max_lines: 8 },
 808                buffer,
 809                None,
 810                window,
 811                cx,
 812            );
 813            editor.focus_handle(cx).focus(window);
 814            editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
 815            editor
 816        });
 817        self.editing_message = Some((
 818            message_id,
 819            EditMessageState {
 820                editor: editor.clone(),
 821            },
 822        ));
 823        cx.notify();
 824    }
 825
 826    fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
 827        self.editing_message.take();
 828        cx.notify();
 829    }
 830
 831    fn confirm_editing_message(
 832        &mut self,
 833        _: &menu::Confirm,
 834        _: &mut Window,
 835        cx: &mut Context<Self>,
 836    ) {
 837        let Some((message_id, state)) = self.editing_message.take() else {
 838            return;
 839        };
 840        let edited_text = state.editor.read(cx).text(cx);
 841        self.thread.update(cx, |thread, cx| {
 842            thread.edit_message(
 843                message_id,
 844                Role::User,
 845                vec![MessageSegment::Text(edited_text)],
 846                cx,
 847            );
 848            for message_id in self.messages_after(message_id) {
 849                thread.delete_message(*message_id, cx);
 850            }
 851        });
 852
 853        let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
 854            return;
 855        };
 856
 857        if model.provider.must_accept_terms(cx) {
 858            cx.notify();
 859            return;
 860        }
 861
 862        self.thread.update(cx, |thread, cx| {
 863            thread.send_to_model(model.model, RequestKind::Chat, cx)
 864        });
 865        cx.notify();
 866    }
 867
 868    fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
 869        self.messages
 870            .iter()
 871            .position(|id| *id == message_id)
 872            .map(|index| &self.messages[index + 1..])
 873            .unwrap_or(&[])
 874    }
 875
 876    fn handle_cancel_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
 877        self.cancel_editing_message(&menu::Cancel, window, cx);
 878    }
 879
 880    fn handle_regenerate_click(
 881        &mut self,
 882        _: &ClickEvent,
 883        window: &mut Window,
 884        cx: &mut Context<Self>,
 885    ) {
 886        self.confirm_editing_message(&menu::Confirm, window, cx);
 887    }
 888
 889    fn handle_feedback_click(
 890        &mut self,
 891        feedback: ThreadFeedback,
 892        window: &mut Window,
 893        cx: &mut Context<Self>,
 894    ) {
 895        match feedback {
 896            ThreadFeedback::Positive => {
 897                let report = self
 898                    .thread
 899                    .update(cx, |thread, cx| thread.report_feedback(feedback, cx));
 900
 901                let this = cx.entity().downgrade();
 902                cx.spawn(async move |_, cx| {
 903                    report.await?;
 904                    this.update(cx, |_this, cx| cx.notify())
 905                })
 906                .detach_and_log_err(cx);
 907            }
 908            ThreadFeedback::Negative => {
 909                self.handle_show_feedback_comments(window, cx);
 910            }
 911        }
 912    }
 913
 914    fn handle_show_feedback_comments(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 915        if self.feedback_message_editor.is_some() {
 916            return;
 917        }
 918
 919        let buffer = cx.new(|cx| {
 920            let empty_string = String::new();
 921            MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)
 922        });
 923
 924        let editor = cx.new(|cx| {
 925            let mut editor = Editor::new(
 926                editor::EditorMode::AutoHeight { max_lines: 4 },
 927                buffer,
 928                None,
 929                window,
 930                cx,
 931            );
 932            editor.set_placeholder_text(
 933                "What went wrong? Share your feedback so we can improve.",
 934                cx,
 935            );
 936            editor
 937        });
 938
 939        editor.read(cx).focus_handle(cx).focus(window);
 940        self.feedback_message_editor = Some(editor);
 941        cx.notify();
 942    }
 943
 944    fn submit_feedback_message(&mut self, cx: &mut Context<Self>) {
 945        let Some(editor) = self.feedback_message_editor.clone() else {
 946            return;
 947        };
 948
 949        let report_task = self.thread.update(cx, |thread, cx| {
 950            thread.report_feedback(ThreadFeedback::Negative, cx)
 951        });
 952
 953        let comments = editor.read(cx).text(cx);
 954        if !comments.is_empty() {
 955            let thread_id = self.thread.read(cx).id().clone();
 956
 957            telemetry::event!("Assistant Thread Feedback Comments", thread_id, comments);
 958        }
 959
 960        self.feedback_message_editor = None;
 961
 962        let this = cx.entity().downgrade();
 963        cx.spawn(async move |_, cx| {
 964            report_task.await?;
 965            this.update(cx, |_this, cx| cx.notify())
 966        })
 967        .detach_and_log_err(cx);
 968    }
 969
 970    fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
 971        let message_id = self.messages[ix];
 972        let Some(message) = self.thread.read(cx).message(message_id) else {
 973            return Empty.into_any();
 974        };
 975
 976        let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
 977            return Empty.into_any();
 978        };
 979
 980        let context_store = self.context_store.clone();
 981        let workspace = self.workspace.clone();
 982
 983        let thread = self.thread.read(cx);
 984        // Get all the data we need from thread before we start using it in closures
 985        let checkpoint = thread.checkpoint_for_message(message_id);
 986        let context = thread.context_for_message(message_id).collect::<Vec<_>>();
 987        let tool_uses = thread.tool_uses_for_message(message_id, cx);
 988        let has_tool_uses = !tool_uses.is_empty();
 989
 990        // Don't render user messages that are just there for returning tool results.
 991        if message.role == Role::User && thread.message_has_tool_results(message_id) {
 992            return Empty.into_any();
 993        }
 994
 995        let allow_editing_message = message.role == Role::User;
 996
 997        let edit_message_editor = self
 998            .editing_message
 999            .as_ref()
1000            .filter(|(id, _)| *id == message_id)
1001            .map(|(_, state)| state.editor.clone());
1002
1003        let first_message = ix == 0;
1004        let show_feedback = ix == self.messages.len() - 1 && message.role != Role::User;
1005
1006        let colors = cx.theme().colors();
1007        let active_color = colors.element_active;
1008        let editor_bg_color = colors.editor_background;
1009        let bg_user_message_header = editor_bg_color.blend(active_color.opacity(0.25));
1010
1011        let feedback_container = h_flex().pt_2().pb_4().px_4().gap_1().justify_between();
1012        let feedback_items = match self.thread.read(cx).feedback() {
1013            Some(feedback) => feedback_container
1014                .child(
1015                    Label::new(match feedback {
1016                        ThreadFeedback::Positive => "Thanks for your feedback!",
1017                        ThreadFeedback::Negative => {
1018                            "We appreciate your feedback and will use it to improve."
1019                        }
1020                    })
1021                    .color(Color::Muted)
1022                    .size(LabelSize::XSmall),
1023                )
1024                .child(
1025                    h_flex()
1026                        .gap_1()
1027                        .child(
1028                            IconButton::new("feedback-thumbs-up", IconName::ThumbsUp)
1029                                .icon_size(IconSize::XSmall)
1030                                .icon_color(match feedback {
1031                                    ThreadFeedback::Positive => Color::Accent,
1032                                    ThreadFeedback::Negative => Color::Ignored,
1033                                })
1034                                .shape(ui::IconButtonShape::Square)
1035                                .tooltip(Tooltip::text("Helpful Response"))
1036                                .on_click(cx.listener(move |this, _, window, cx| {
1037                                    this.handle_feedback_click(
1038                                        ThreadFeedback::Positive,
1039                                        window,
1040                                        cx,
1041                                    );
1042                                })),
1043                        )
1044                        .child(
1045                            IconButton::new("feedback-thumbs-down", IconName::ThumbsDown)
1046                                .icon_size(IconSize::XSmall)
1047                                .icon_color(match feedback {
1048                                    ThreadFeedback::Positive => Color::Ignored,
1049                                    ThreadFeedback::Negative => Color::Accent,
1050                                })
1051                                .shape(ui::IconButtonShape::Square)
1052                                .tooltip(Tooltip::text("Not Helpful"))
1053                                .on_click(cx.listener(move |this, _, window, cx| {
1054                                    this.handle_feedback_click(
1055                                        ThreadFeedback::Negative,
1056                                        window,
1057                                        cx,
1058                                    );
1059                                })),
1060                        ),
1061                )
1062                .into_any_element(),
1063            None => feedback_container
1064                .child(
1065                    Label::new(
1066                        "Rating the thread sends all of your current conversation to the Zed team.",
1067                    )
1068                    .color(Color::Muted)
1069                    .size(LabelSize::XSmall),
1070                )
1071                .child(
1072                    h_flex()
1073                        .gap_1()
1074                        .child(
1075                            IconButton::new("feedback-thumbs-up", IconName::ThumbsUp)
1076                                .icon_size(IconSize::XSmall)
1077                                .icon_color(Color::Ignored)
1078                                .shape(ui::IconButtonShape::Square)
1079                                .tooltip(Tooltip::text("Helpful Response"))
1080                                .on_click(cx.listener(move |this, _, window, cx| {
1081                                    this.handle_feedback_click(
1082                                        ThreadFeedback::Positive,
1083                                        window,
1084                                        cx,
1085                                    );
1086                                })),
1087                        )
1088                        .child(
1089                            IconButton::new("feedback-thumbs-down", IconName::ThumbsDown)
1090                                .icon_size(IconSize::XSmall)
1091                                .icon_color(Color::Ignored)
1092                                .shape(ui::IconButtonShape::Square)
1093                                .tooltip(Tooltip::text("Not Helpful"))
1094                                .on_click(cx.listener(move |this, _, window, cx| {
1095                                    this.handle_feedback_click(
1096                                        ThreadFeedback::Negative,
1097                                        window,
1098                                        cx,
1099                                    );
1100                                })),
1101                        ),
1102                )
1103                .into_any_element(),
1104        };
1105
1106        let message_is_empty = message.should_display_content();
1107        let has_content = !message_is_empty || !context.is_empty();
1108
1109        let message_content =
1110            has_content.then(|| {
1111                v_flex()
1112                    .gap_1p5()
1113                    .when(!message_is_empty, |parent| {
1114                        parent.child(
1115                            if let Some(edit_message_editor) = edit_message_editor.clone() {
1116                                div()
1117                                    .key_context("EditMessageEditor")
1118                                    .on_action(cx.listener(Self::cancel_editing_message))
1119                                    .on_action(cx.listener(Self::confirm_editing_message))
1120                                    .min_h_6()
1121                                    .child(edit_message_editor)
1122                                    .into_any()
1123                            } else {
1124                                div()
1125                                    .min_h_6()
1126                                    .text_ui(cx)
1127                                    .child(self.render_message_content(
1128                                        message_id,
1129                                        rendered_message,
1130                                        has_tool_uses,
1131                                        window,
1132                                        cx,
1133                                    ))
1134                                    .into_any()
1135                            },
1136                        )
1137                    })
1138                    .when(!context.is_empty(), |parent| {
1139                        parent.child(h_flex().flex_wrap().gap_1().children(
1140                            context.into_iter().map(|context| {
1141                                let context_id = context.id();
1142                                ContextPill::added(
1143                                    AddedContext::new(context, cx),
1144                                    false,
1145                                    false,
1146                                    None,
1147                                )
1148                                .on_click(Rc::new(cx.listener({
1149                                    let workspace = workspace.clone();
1150                                    let context_store = context_store.clone();
1151                                    move |_, _, window, cx| {
1152                                        if let Some(workspace) = workspace.upgrade() {
1153                                            open_context(
1154                                                context_id,
1155                                                context_store.clone(),
1156                                                workspace,
1157                                                window,
1158                                                cx,
1159                                            );
1160                                            cx.notify();
1161                                        }
1162                                    }
1163                                })))
1164                            }),
1165                        ))
1166                    })
1167            });
1168
1169        let styled_message = match message.role {
1170            Role::User => v_flex()
1171                .id(("message-container", ix))
1172                .map(|this| {
1173                    if first_message {
1174                        this.pt_2()
1175                    } else {
1176                        this.pt_4()
1177                    }
1178                })
1179                .pb_4()
1180                .pl_2()
1181                .pr_2p5()
1182                .child(
1183                    v_flex()
1184                        .bg(colors.editor_background)
1185                        .rounded_lg()
1186                        .border_1()
1187                        .border_color(colors.border)
1188                        .shadow_md()
1189                        .child(
1190                            h_flex()
1191                                .py_1()
1192                                .pl_2()
1193                                .pr_1()
1194                                .bg(bg_user_message_header)
1195                                .border_b_1()
1196                                .border_color(colors.border)
1197                                .justify_between()
1198                                .rounded_t_md()
1199                                .child(
1200                                    h_flex()
1201                                        .gap_1p5()
1202                                        .child(
1203                                            Icon::new(IconName::PersonCircle)
1204                                                .size(IconSize::XSmall)
1205                                                .color(Color::Muted),
1206                                        )
1207                                        .child(
1208                                            Label::new("You")
1209                                                .size(LabelSize::Small)
1210                                                .color(Color::Muted),
1211                                        ),
1212                                )
1213                                .child(
1214                                    h_flex()
1215                                        .gap_1()
1216                                        .when_some(
1217                                            edit_message_editor.clone(),
1218                                            |this, edit_message_editor| {
1219                                                let focus_handle =
1220                                                    edit_message_editor.focus_handle(cx);
1221                                                this.child(
1222                                                    Button::new("cancel-edit-message", "Cancel")
1223                                                        .label_size(LabelSize::Small)
1224                                                        .key_binding(
1225                                                            KeyBinding::for_action_in(
1226                                                                &menu::Cancel,
1227                                                                &focus_handle,
1228                                                                window,
1229                                                                cx,
1230                                                            )
1231                                                            .map(|kb| kb.size(rems_from_px(12.))),
1232                                                        )
1233                                                        .on_click(
1234                                                            cx.listener(Self::handle_cancel_click),
1235                                                        ),
1236                                                )
1237                                                .child(
1238                                                    Button::new(
1239                                                        "confirm-edit-message",
1240                                                        "Regenerate",
1241                                                    )
1242                                                    .label_size(LabelSize::Small)
1243                                                    .key_binding(
1244                                                        KeyBinding::for_action_in(
1245                                                            &menu::Confirm,
1246                                                            &focus_handle,
1247                                                            window,
1248                                                            cx,
1249                                                        )
1250                                                        .map(|kb| kb.size(rems_from_px(12.))),
1251                                                    )
1252                                                    .on_click(
1253                                                        cx.listener(Self::handle_regenerate_click),
1254                                                    ),
1255                                                )
1256                                            },
1257                                        )
1258                                        .when(
1259                                            edit_message_editor.is_none() && allow_editing_message,
1260                                            |this| {
1261                                                this.child(
1262                                                    Button::new("edit-message", "Edit")
1263                                                        .label_size(LabelSize::Small)
1264                                                        .on_click(cx.listener({
1265                                                            let message_segments =
1266                                                                message.segments.clone();
1267                                                            move |this, _, window, cx| {
1268                                                                this.start_editing_message(
1269                                                                    message_id,
1270                                                                    &message_segments,
1271                                                                    window,
1272                                                                    cx,
1273                                                                );
1274                                                            }
1275                                                        })),
1276                                                )
1277                                            },
1278                                        ),
1279                                ),
1280                        )
1281                        .child(div().p_2().children(message_content)),
1282                ),
1283            Role::Assistant => v_flex()
1284                .id(("message-container", ix))
1285                .ml_2()
1286                .pl_2()
1287                .pr_4()
1288                .border_l_1()
1289                .border_color(cx.theme().colors().border_variant)
1290                .children(message_content)
1291                .gap_2p5()
1292                .pb_2p5()
1293                .when(!tool_uses.is_empty(), |parent| {
1294                    parent.child(
1295                        div().children(
1296                            tool_uses
1297                                .into_iter()
1298                                .map(|tool_use| self.render_tool_use(tool_use, window, cx)),
1299                        ),
1300                    )
1301                }),
1302            Role::System => div().id(("message-container", ix)).py_1().px_2().child(
1303                v_flex()
1304                    .bg(colors.editor_background)
1305                    .rounded_sm()
1306                    .child(div().p_4().children(message_content)),
1307            ),
1308        };
1309
1310        v_flex()
1311            .w_full()
1312            .when(first_message, |parent| {
1313                parent.child(self.render_rules_item(cx))
1314            })
1315            .when_some(checkpoint, |parent, checkpoint| {
1316                let mut is_pending = false;
1317                let mut error = None;
1318                if let Some(last_restore_checkpoint) =
1319                    self.thread.read(cx).last_restore_checkpoint()
1320                {
1321                    if last_restore_checkpoint.message_id() == message_id {
1322                        match last_restore_checkpoint {
1323                            LastRestoreCheckpoint::Pending { .. } => is_pending = true,
1324                            LastRestoreCheckpoint::Error { error: err, .. } => {
1325                                error = Some(err.clone());
1326                            }
1327                        }
1328                    }
1329                }
1330
1331                let restore_checkpoint_button =
1332                    Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
1333                        .icon(if error.is_some() {
1334                            IconName::XCircle
1335                        } else {
1336                            IconName::Undo
1337                        })
1338                        .icon_size(IconSize::XSmall)
1339                        .icon_position(IconPosition::Start)
1340                        .icon_color(if error.is_some() {
1341                            Some(Color::Error)
1342                        } else {
1343                            None
1344                        })
1345                        .label_size(LabelSize::XSmall)
1346                        .disabled(is_pending)
1347                        .on_click(cx.listener(move |this, _, _window, cx| {
1348                            this.thread.update(cx, |thread, cx| {
1349                                thread
1350                                    .restore_checkpoint(checkpoint.clone(), cx)
1351                                    .detach_and_log_err(cx);
1352                            });
1353                        }));
1354
1355                let restore_checkpoint_button = if is_pending {
1356                    restore_checkpoint_button
1357                        .with_animation(
1358                            ("pulsating-restore-checkpoint-button", ix),
1359                            Animation::new(Duration::from_secs(2))
1360                                .repeat()
1361                                .with_easing(pulsating_between(0.6, 1.)),
1362                            |label, delta| label.alpha(delta),
1363                        )
1364                        .into_any_element()
1365                } else if let Some(error) = error {
1366                    restore_checkpoint_button
1367                        .tooltip(Tooltip::text(error.to_string()))
1368                        .into_any_element()
1369                } else {
1370                    restore_checkpoint_button.into_any_element()
1371                };
1372
1373                parent.child(
1374                    h_flex()
1375                        .pt_2p5()
1376                        .px_2p5()
1377                        .w_full()
1378                        .gap_1()
1379                        .child(ui::Divider::horizontal())
1380                        .child(restore_checkpoint_button)
1381                        .child(ui::Divider::horizontal()),
1382                )
1383            })
1384            .child(styled_message)
1385            .when(
1386                show_feedback && !self.thread.read(cx).is_generating(),
1387                |parent| {
1388                    parent.child(feedback_items).when_some(
1389                        self.feedback_message_editor.clone(),
1390                        |parent, feedback_editor| {
1391                            let focus_handle = feedback_editor.focus_handle(cx);
1392                            parent.child(
1393                                v_flex()
1394                                    .key_context("AgentFeedbackMessageEditor")
1395                                    .on_action(cx.listener(|this, _: &menu::Cancel, _, cx| {
1396                                        this.feedback_message_editor = None;
1397                                        cx.notify();
1398                                    }))
1399                                    .on_action(cx.listener(|this, _: &menu::Confirm, _, cx| {
1400                                        this.submit_feedback_message(cx);
1401                                        cx.notify();
1402                                    }))
1403                                    .on_action(cx.listener(Self::confirm_editing_message))
1404                                    .mx_4()
1405                                    .mb_3()
1406                                    .p_2()
1407                                    .rounded_md()
1408                                    .border_1()
1409                                    .border_color(cx.theme().colors().border)
1410                                    .bg(cx.theme().colors().editor_background)
1411                                    .child(feedback_editor)
1412                                    .child(
1413                                        h_flex()
1414                                            .gap_1()
1415                                            .justify_end()
1416                                            .child(
1417                                                Button::new("dismiss-feedback-message", "Cancel")
1418                                                    .label_size(LabelSize::Small)
1419                                                    .key_binding(
1420                                                        KeyBinding::for_action_in(
1421                                                            &menu::Cancel,
1422                                                            &focus_handle,
1423                                                            window,
1424                                                            cx,
1425                                                        )
1426                                                        .map(|kb| kb.size(rems_from_px(10.))),
1427                                                    )
1428                                                    .on_click(cx.listener(|this, _, _, cx| {
1429                                                        this.feedback_message_editor = None;
1430                                                        cx.notify();
1431                                                    })),
1432                                            )
1433                                            .child(
1434                                                Button::new(
1435                                                    "submit-feedback-message",
1436                                                    "Share Feedback",
1437                                                )
1438                                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
1439                                                .label_size(LabelSize::Small)
1440                                                .key_binding(
1441                                                    KeyBinding::for_action_in(
1442                                                        &menu::Confirm,
1443                                                        &focus_handle,
1444                                                        window,
1445                                                        cx,
1446                                                    )
1447                                                    .map(|kb| kb.size(rems_from_px(10.))),
1448                                                )
1449                                                .on_click(cx.listener(|this, _, _, cx| {
1450                                                    this.submit_feedback_message(cx);
1451                                                    cx.notify();
1452                                                })),
1453                                            ),
1454                                    ),
1455                            )
1456                        },
1457                    )
1458                },
1459            )
1460            .into_any()
1461    }
1462
1463    fn render_message_content(
1464        &self,
1465        message_id: MessageId,
1466        rendered_message: &RenderedMessage,
1467        has_tool_uses: bool,
1468        window: &Window,
1469        cx: &Context<Self>,
1470    ) -> impl IntoElement {
1471        let is_last_message = self.messages.last() == Some(&message_id);
1472        let is_generating = self.thread.read(cx).is_generating();
1473        let pending_thinking_segment_index = if is_generating && is_last_message && !has_tool_uses {
1474            rendered_message
1475                .segments
1476                .iter()
1477                .enumerate()
1478                .next_back()
1479                .filter(|(_, segment)| matches!(segment, RenderedMessageSegment::Thinking { .. }))
1480                .map(|(index, _)| index)
1481        } else {
1482            None
1483        };
1484
1485        div()
1486            .text_ui(cx)
1487            .gap_2()
1488            .children(
1489                rendered_message.segments.iter().enumerate().map(
1490                    |(index, segment)| match segment {
1491                        RenderedMessageSegment::Thinking {
1492                            content,
1493                            scroll_handle,
1494                        } => self
1495                            .render_message_thinking_segment(
1496                                message_id,
1497                                index,
1498                                content.clone(),
1499                                &scroll_handle,
1500                                Some(index) == pending_thinking_segment_index,
1501                                window,
1502                                cx,
1503                            )
1504                            .into_any_element(),
1505                        RenderedMessageSegment::Text(markdown) => div()
1506                            .child(
1507                                MarkdownElement::new(
1508                                    markdown.clone(),
1509                                    default_markdown_style(window, cx),
1510                                )
1511                                .on_url_click({
1512                                    let workspace = self.workspace.clone();
1513                                    move |text, window, cx| {
1514                                        open_markdown_link(text, workspace.clone(), window, cx);
1515                                    }
1516                                }),
1517                            )
1518                            .into_any_element(),
1519                    },
1520                ),
1521            )
1522    }
1523
1524    fn tool_card_border_color(&self, cx: &Context<Self>) -> Hsla {
1525        cx.theme().colors().border.opacity(0.5)
1526    }
1527
1528    fn tool_card_header_bg(&self, cx: &Context<Self>) -> Hsla {
1529        cx.theme()
1530            .colors()
1531            .element_background
1532            .blend(cx.theme().colors().editor_foreground.opacity(0.025))
1533    }
1534
1535    fn render_message_thinking_segment(
1536        &self,
1537        message_id: MessageId,
1538        ix: usize,
1539        markdown: Entity<Markdown>,
1540        scroll_handle: &ScrollHandle,
1541        pending: bool,
1542        window: &Window,
1543        cx: &Context<Self>,
1544    ) -> impl IntoElement {
1545        let is_open = self
1546            .expanded_thinking_segments
1547            .get(&(message_id, ix))
1548            .copied()
1549            .unwrap_or_default();
1550
1551        let editor_bg = cx.theme().colors().editor_background;
1552
1553        div().pt_0p5().pb_2().child(
1554            v_flex()
1555                .rounded_lg()
1556                .border_1()
1557                .border_color(self.tool_card_border_color(cx))
1558                .child(
1559                    h_flex()
1560                        .group("disclosure-header")
1561                        .justify_between()
1562                        .py_1()
1563                        .px_2()
1564                        .bg(self.tool_card_header_bg(cx))
1565                        .map(|this| {
1566                            if pending || is_open {
1567                                this.rounded_t_md()
1568                                    .border_b_1()
1569                                    .border_color(self.tool_card_border_color(cx))
1570                            } else {
1571                                this.rounded_md()
1572                            }
1573                        })
1574                        .child(
1575                            h_flex()
1576                                .gap_1p5()
1577                                .child(
1578                                    Icon::new(IconName::Brain)
1579                                        .size(IconSize::XSmall)
1580                                        .color(Color::Muted),
1581                                )
1582                                .child({
1583                                    if pending {
1584                                        Label::new("Thinking…")
1585                                            .size(LabelSize::Small)
1586                                            .buffer_font(cx)
1587                                            .with_animation(
1588                                                "pulsating-label",
1589                                                Animation::new(Duration::from_secs(2))
1590                                                    .repeat()
1591                                                    .with_easing(pulsating_between(0.4, 0.8)),
1592                                                |label, delta| label.alpha(delta),
1593                                            )
1594                                            .into_any_element()
1595                                    } else {
1596                                        Label::new("Thought Process")
1597                                            .size(LabelSize::Small)
1598                                            .buffer_font(cx)
1599                                            .into_any_element()
1600                                    }
1601                                }),
1602                        )
1603                        .child(
1604                            h_flex()
1605                                .gap_1()
1606                                .child(
1607                                    div().visible_on_hover("disclosure-header").child(
1608                                        Disclosure::new("thinking-disclosure", is_open)
1609                                            .opened_icon(IconName::ChevronUp)
1610                                            .closed_icon(IconName::ChevronDown)
1611                                            .on_click(cx.listener({
1612                                                move |this, _event, _window, _cx| {
1613                                                    let is_open = this
1614                                                        .expanded_thinking_segments
1615                                                        .entry((message_id, ix))
1616                                                        .or_insert(false);
1617
1618                                                    *is_open = !*is_open;
1619                                                }
1620                                            })),
1621                                    ),
1622                                )
1623                                .child({
1624                                    let (icon_name, color, animated) = if pending {
1625                                        (IconName::ArrowCircle, Color::Accent, true)
1626                                    } else {
1627                                        (IconName::Check, Color::Success, false)
1628                                    };
1629
1630                                    let icon =
1631                                        Icon::new(icon_name).color(color).size(IconSize::Small);
1632
1633                                    if animated {
1634                                        icon.with_animation(
1635                                            "arrow-circle",
1636                                            Animation::new(Duration::from_secs(2)).repeat(),
1637                                            |icon, delta| {
1638                                                icon.transform(Transformation::rotate(percentage(
1639                                                    delta,
1640                                                )))
1641                                            },
1642                                        )
1643                                        .into_any_element()
1644                                    } else {
1645                                        icon.into_any_element()
1646                                    }
1647                                }),
1648                        ),
1649                )
1650                .when(pending && !is_open, |this| {
1651                    let gradient_overlay = div()
1652                        .rounded_b_lg()
1653                        .h_20()
1654                        .absolute()
1655                        .w_full()
1656                        .bottom_0()
1657                        .left_0()
1658                        .bg(linear_gradient(
1659                            180.,
1660                            linear_color_stop(editor_bg, 1.),
1661                            linear_color_stop(editor_bg.opacity(0.2), 0.),
1662                        ));
1663
1664                    this.child(
1665                        div()
1666                            .relative()
1667                            .bg(editor_bg)
1668                            .rounded_b_lg()
1669                            .child(
1670                                div()
1671                                    .id(("thinking-content", ix))
1672                                    .p_2()
1673                                    .h_20()
1674                                    .track_scroll(scroll_handle)
1675                                    .text_ui_sm(cx)
1676                                    .child(
1677                                        MarkdownElement::new(
1678                                            markdown.clone(),
1679                                            default_markdown_style(window, cx),
1680                                        )
1681                                        .on_url_click({
1682                                            let workspace = self.workspace.clone();
1683                                            move |text, window, cx| {
1684                                                open_markdown_link(
1685                                                    text,
1686                                                    workspace.clone(),
1687                                                    window,
1688                                                    cx,
1689                                                );
1690                                            }
1691                                        }),
1692                                    )
1693                                    .overflow_hidden(),
1694                            )
1695                            .child(gradient_overlay),
1696                    )
1697                })
1698                .when(is_open, |this| {
1699                    this.child(
1700                        div()
1701                            .id(("thinking-content", ix))
1702                            .h_full()
1703                            .p_2()
1704                            .rounded_b_lg()
1705                            .bg(editor_bg)
1706                            .text_ui_sm(cx)
1707                            .child(
1708                                MarkdownElement::new(
1709                                    markdown.clone(),
1710                                    default_markdown_style(window, cx),
1711                                )
1712                                .on_url_click({
1713                                    let workspace = self.workspace.clone();
1714                                    move |text, window, cx| {
1715                                        open_markdown_link(text, workspace.clone(), window, cx);
1716                                    }
1717                                }),
1718                            ),
1719                    )
1720                }),
1721        )
1722    }
1723
1724    fn render_tool_use(
1725        &self,
1726        tool_use: ToolUse,
1727        window: &mut Window,
1728        cx: &mut Context<Self>,
1729    ) -> impl IntoElement + use<> {
1730        let is_open = self
1731            .expanded_tool_uses
1732            .get(&tool_use.id)
1733            .copied()
1734            .unwrap_or_default();
1735
1736        let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_));
1737
1738        let fs = self
1739            .workspace
1740            .upgrade()
1741            .map(|workspace| workspace.read(cx).app_state().fs.clone());
1742        let needs_confirmation = matches!(&tool_use.status, ToolUseStatus::NeedsConfirmation);
1743
1744        let status_icons = div().child(match &tool_use.status {
1745            ToolUseStatus::Pending | ToolUseStatus::NeedsConfirmation => {
1746                let icon = Icon::new(IconName::Warning)
1747                    .color(Color::Warning)
1748                    .size(IconSize::Small);
1749                icon.into_any_element()
1750            }
1751            ToolUseStatus::Running => {
1752                let icon = Icon::new(IconName::ArrowCircle)
1753                    .color(Color::Accent)
1754                    .size(IconSize::Small);
1755                icon.with_animation(
1756                    "arrow-circle",
1757                    Animation::new(Duration::from_secs(2)).repeat(),
1758                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
1759                )
1760                .into_any_element()
1761            }
1762            ToolUseStatus::Finished(_) => div().w_0().into_any_element(),
1763            ToolUseStatus::Error(_) => {
1764                let icon = Icon::new(IconName::Close)
1765                    .color(Color::Error)
1766                    .size(IconSize::Small);
1767                icon.into_any_element()
1768            }
1769        });
1770
1771        let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned();
1772        let results_content_container = || v_flex().p_2().gap_0p5();
1773
1774        let results_content = v_flex()
1775            .gap_1()
1776            .child(
1777                results_content_container()
1778                    .child(
1779                        Label::new("Input")
1780                            .size(LabelSize::XSmall)
1781                            .color(Color::Muted)
1782                            .buffer_font(cx),
1783                    )
1784                    .child(
1785                        div()
1786                            .w_full()
1787                            .text_ui_sm(cx)
1788                            .children(rendered_tool_use.as_ref().map(|rendered| {
1789                                MarkdownElement::new(
1790                                    rendered.input.clone(),
1791                                    tool_use_markdown_style(window, cx),
1792                                )
1793                                .on_url_click({
1794                                    let workspace = self.workspace.clone();
1795                                    move |text, window, cx| {
1796                                        open_markdown_link(text, workspace.clone(), window, cx);
1797                                    }
1798                                })
1799                            })),
1800                    ),
1801            )
1802            .map(|container| match tool_use.status {
1803                ToolUseStatus::Finished(_) => container.child(
1804                    results_content_container()
1805                        .border_t_1()
1806                        .border_color(self.tool_card_border_color(cx))
1807                        .child(
1808                            Label::new("Result")
1809                                .size(LabelSize::XSmall)
1810                                .color(Color::Muted)
1811                                .buffer_font(cx),
1812                        )
1813                        .child(div().w_full().text_ui_sm(cx).children(
1814                            rendered_tool_use.as_ref().map(|rendered| {
1815                                MarkdownElement::new(
1816                                    rendered.output.clone(),
1817                                    tool_use_markdown_style(window, cx),
1818                                )
1819                                .on_url_click({
1820                                    let workspace = self.workspace.clone();
1821                                    move |text, window, cx| {
1822                                        open_markdown_link(text, workspace.clone(), window, cx);
1823                                    }
1824                                })
1825                            }),
1826                        )),
1827                ),
1828                ToolUseStatus::Running => container.child(
1829                    results_content_container().child(
1830                        h_flex()
1831                            .gap_1()
1832                            .pb_1()
1833                            .border_t_1()
1834                            .border_color(self.tool_card_border_color(cx))
1835                            .child(
1836                                Icon::new(IconName::ArrowCircle)
1837                                    .size(IconSize::Small)
1838                                    .color(Color::Accent)
1839                                    .with_animation(
1840                                        "arrow-circle",
1841                                        Animation::new(Duration::from_secs(2)).repeat(),
1842                                        |icon, delta| {
1843                                            icon.transform(Transformation::rotate(percentage(
1844                                                delta,
1845                                            )))
1846                                        },
1847                                    ),
1848                            )
1849                            .child(
1850                                Label::new("Running…")
1851                                    .size(LabelSize::XSmall)
1852                                    .color(Color::Muted)
1853                                    .buffer_font(cx),
1854                            ),
1855                    ),
1856                ),
1857                ToolUseStatus::Error(_) => container.child(
1858                    results_content_container()
1859                        .border_t_1()
1860                        .border_color(self.tool_card_border_color(cx))
1861                        .child(
1862                            Label::new("Error")
1863                                .size(LabelSize::XSmall)
1864                                .color(Color::Muted)
1865                                .buffer_font(cx),
1866                        )
1867                        .child(
1868                            div()
1869                                .text_ui_sm(cx)
1870                                .children(rendered_tool_use.as_ref().map(|rendered| {
1871                                    MarkdownElement::new(
1872                                        rendered.output.clone(),
1873                                        tool_use_markdown_style(window, cx),
1874                                    )
1875                                    .on_url_click({
1876                                        let workspace = self.workspace.clone();
1877                                        move |text, window, cx| {
1878                                            open_markdown_link(text, workspace.clone(), window, cx);
1879                                        }
1880                                    })
1881                                })),
1882                        ),
1883                ),
1884                ToolUseStatus::Pending => container,
1885                ToolUseStatus::NeedsConfirmation => container.child(
1886                    results_content_container()
1887                        .border_t_1()
1888                        .border_color(self.tool_card_border_color(cx))
1889                        .child(
1890                            Label::new("Asking Permission")
1891                                .size(LabelSize::Small)
1892                                .color(Color::Muted)
1893                                .buffer_font(cx),
1894                        ),
1895                ),
1896            });
1897
1898        let gradient_overlay = |color: Hsla| {
1899            div()
1900                .h_full()
1901                .absolute()
1902                .w_12()
1903                .bottom_0()
1904                .map(|element| {
1905                    if is_status_finished {
1906                        element.right_6()
1907                    } else {
1908                        element.right(px(44.))
1909                    }
1910                })
1911                .bg(linear_gradient(
1912                    90.,
1913                    linear_color_stop(color, 1.),
1914                    linear_color_stop(color.opacity(0.2), 0.),
1915                ))
1916        };
1917
1918        div().map(|element| {
1919            if !tool_use.needs_confirmation {
1920                element.child(
1921                    v_flex()
1922                        .child(
1923                            h_flex()
1924                                .group("disclosure-header")
1925                                .relative()
1926                                .gap_1p5()
1927                                .justify_between()
1928                                .opacity(0.8)
1929                                .hover(|style| style.opacity(1.))
1930                                .when(!is_status_finished, |this| this.pr_2())
1931                                .child(
1932                                    h_flex()
1933                                        .id("tool-label-container")
1934                                        .gap_1p5()
1935                                        .max_w_full()
1936                                        .overflow_x_scroll()
1937                                        .child(
1938                                            Icon::new(tool_use.icon)
1939                                                .size(IconSize::XSmall)
1940                                                .color(Color::Muted),
1941                                        )
1942                                        .child(
1943                                            h_flex().pr_8().text_ui_sm(cx).children(
1944                                                rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
1945                                                    open_markdown_link(text, workspace.clone(), window, cx);
1946                                                }}))
1947                                            ),
1948                                        ),
1949                                )
1950                                .child(
1951                                    h_flex()
1952                                        .gap_1()
1953                                        .child(
1954                                            div().visible_on_hover("disclosure-header").child(
1955                                                Disclosure::new("tool-use-disclosure", is_open)
1956                                                    .opened_icon(IconName::ChevronUp)
1957                                                    .closed_icon(IconName::ChevronDown)
1958                                                    .on_click(cx.listener({
1959                                                        let tool_use_id = tool_use.id.clone();
1960                                                        move |this, _event, _window, _cx| {
1961                                                            let is_open = this
1962                                                                .expanded_tool_uses
1963                                                                .entry(tool_use_id.clone())
1964                                                                .or_insert(false);
1965
1966                                                            *is_open = !*is_open;
1967                                                        }
1968                                                    })),
1969                                            ),
1970                                        )
1971                                        .child(status_icons),
1972                                )
1973                                .child(gradient_overlay(cx.theme().colors().panel_background)),
1974                        )
1975                        .map(|parent| {
1976                            if !is_open {
1977                                return parent;
1978                            }
1979
1980                            parent.child(
1981                                v_flex()
1982                                    .mt_1()
1983                                    .border_1()
1984                                    .border_color(self.tool_card_border_color(cx))
1985                                    .bg(cx.theme().colors().editor_background)
1986                                    .rounded_lg()
1987                                    .child(results_content),
1988                            )
1989                        }),
1990                )
1991            } else {
1992                v_flex()
1993                    .rounded_lg()
1994                    .border_1()
1995                    .border_color(self.tool_card_border_color(cx))
1996                    .overflow_hidden()
1997                    .child(
1998                        h_flex()
1999                            .group("disclosure-header")
2000                            .relative()
2001                            .justify_between()
2002                            .py_1()
2003                            .map(|element| {
2004                                if is_status_finished {
2005                                    element.pl_2().pr_0p5()
2006                                } else {
2007                                    element.px_2()
2008                                }
2009                            })
2010                            .bg(self.tool_card_header_bg(cx))
2011                            .map(|element| {
2012                                if is_open {
2013                                    element.border_b_1().rounded_t_md()
2014                                } else if needs_confirmation {
2015                                    element.rounded_t_md()
2016                                } else {
2017                                    element.rounded_md()
2018                                }
2019                            })
2020                            .border_color(self.tool_card_border_color(cx))
2021                            .child(
2022                                h_flex()
2023                                    .id("tool-label-container")
2024                                    .gap_1p5()
2025                                    .max_w_full()
2026                                    .overflow_x_scroll()
2027                                    .child(
2028                                        Icon::new(tool_use.icon)
2029                                            .size(IconSize::XSmall)
2030                                            .color(Color::Muted),
2031                                    )
2032                                    .child(
2033                                        h_flex().pr_8().text_ui_sm(cx).children(
2034                                            rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
2035                                                open_markdown_link(text, workspace.clone(), window, cx);
2036                                            }}))
2037                                        ),
2038                                    ),
2039                            )
2040                            .child(
2041                                h_flex()
2042                                    .gap_1()
2043                                    .child(
2044                                        div().visible_on_hover("disclosure-header").child(
2045                                            Disclosure::new("tool-use-disclosure", is_open)
2046                                                .opened_icon(IconName::ChevronUp)
2047                                                .closed_icon(IconName::ChevronDown)
2048                                                .on_click(cx.listener({
2049                                                    let tool_use_id = tool_use.id.clone();
2050                                                    move |this, _event, _window, _cx| {
2051                                                        let is_open = this
2052                                                            .expanded_tool_uses
2053                                                            .entry(tool_use_id.clone())
2054                                                            .or_insert(false);
2055
2056                                                        *is_open = !*is_open;
2057                                                    }
2058                                                })),
2059                                        ),
2060                                    )
2061                                    .child(status_icons),
2062                            )
2063                            .child(gradient_overlay(self.tool_card_header_bg(cx))),
2064                    )
2065                    .map(|parent| {
2066                        if !is_open {
2067                            return parent;
2068                        }
2069
2070                        parent.child(
2071                            v_flex()
2072                                .bg(cx.theme().colors().editor_background)
2073                                .map(|element| {
2074                                    if  needs_confirmation {
2075                                        element.rounded_none()
2076                                    } else {
2077                                        element.rounded_b_lg()
2078                                    }
2079                                })
2080                                .child(results_content),
2081                        )
2082                    })
2083                    .when(needs_confirmation, |this| {
2084                        this.child(
2085                            h_flex()
2086                                .py_1()
2087                                .pl_2()
2088                                .pr_1()
2089                                .gap_1()
2090                                .justify_between()
2091                                .bg(cx.theme().colors().editor_background)
2092                                .border_t_1()
2093                                .border_color(self.tool_card_border_color(cx))
2094                                .rounded_b_lg()
2095                                .child(Label::new("Action Confirmation").color(Color::Muted).size(LabelSize::Small))
2096                                .child(
2097                                    h_flex()
2098                                        .gap_0p5()
2099                                        .child({
2100                                            let tool_id = tool_use.id.clone();
2101                                            Button::new(
2102                                                "always-allow-tool-action",
2103                                                "Always Allow",
2104                                            )
2105                                            .label_size(LabelSize::Small)
2106                                            .icon(IconName::CheckDouble)
2107                                            .icon_position(IconPosition::Start)
2108                                            .icon_size(IconSize::Small)
2109                                            .icon_color(Color::Success)
2110                                            .tooltip(move |window, cx|  {
2111                                                Tooltip::with_meta(
2112                                                    "Never ask for permission",
2113                                                    None,
2114                                                    "Restore the original behavior in your Agent Panel settings",
2115                                                    window,
2116                                                    cx,
2117                                                )
2118                                            })
2119                                            .on_click(cx.listener(
2120                                                move |this, event, window, cx| {
2121                                                    if let Some(fs) = fs.clone() {
2122                                                        update_settings_file::<AssistantSettings>(
2123                                                            fs.clone(),
2124                                                            cx,
2125                                                            |settings, _| {
2126                                                                settings.set_always_allow_tool_actions(true);
2127                                                            },
2128                                                        );
2129                                                    }
2130                                                    this.handle_allow_tool(
2131                                                        tool_id.clone(),
2132                                                        event,
2133                                                        window,
2134                                                        cx,
2135                                                    )
2136                                                },
2137                                            ))
2138                                        })
2139                                        .child(ui::Divider::vertical())
2140                                        .child({
2141                                            let tool_id = tool_use.id.clone();
2142                                            Button::new("allow-tool-action", "Allow")
2143                                                .label_size(LabelSize::Small)
2144                                                .icon(IconName::Check)
2145                                                .icon_position(IconPosition::Start)
2146                                                .icon_size(IconSize::Small)
2147                                                .icon_color(Color::Success)
2148                                                .on_click(cx.listener(
2149                                                    move |this, event, window, cx| {
2150                                                        this.handle_allow_tool(
2151                                                            tool_id.clone(),
2152                                                            event,
2153                                                            window,
2154                                                            cx,
2155                                                        )
2156                                                    },
2157                                                ))
2158                                        })
2159                                        .child({
2160                                            let tool_id = tool_use.id.clone();
2161                                            let tool_name: Arc<str> = tool_use.name.into();
2162                                            Button::new("deny-tool", "Deny")
2163                                                .label_size(LabelSize::Small)
2164                                                .icon(IconName::Close)
2165                                                .icon_position(IconPosition::Start)
2166                                                .icon_size(IconSize::Small)
2167                                                .icon_color(Color::Error)
2168                                                .on_click(cx.listener(
2169                                                    move |this, event, window, cx| {
2170                                                        this.handle_deny_tool(
2171                                                            tool_id.clone(),
2172                                                            tool_name.clone(),
2173                                                            event,
2174                                                            window,
2175                                                            cx,
2176                                                        )
2177                                                    },
2178                                                ))
2179                                        }),
2180                                ),
2181                        )
2182                    })
2183            }
2184        })
2185    }
2186
2187    fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
2188        let Some(system_prompt_context) = self.thread.read(cx).system_prompt_context().as_ref()
2189        else {
2190            return div().into_any();
2191        };
2192
2193        let rules_files = system_prompt_context
2194            .worktrees
2195            .iter()
2196            .filter_map(|worktree| worktree.rules_file.as_ref())
2197            .collect::<Vec<_>>();
2198
2199        let label_text = match rules_files.as_slice() {
2200            &[] => return div().into_any(),
2201            &[rules_file] => {
2202                format!("Using {:?} file", rules_file.rel_path)
2203            }
2204            rules_files => {
2205                format!("Using {} rules files", rules_files.len())
2206            }
2207        };
2208
2209        div()
2210            .pt_1()
2211            .px_2p5()
2212            .child(
2213                h_flex()
2214                    .w_full()
2215                    .gap_0p5()
2216                    .child(
2217                        h_flex()
2218                            .gap_1p5()
2219                            .child(
2220                                Icon::new(IconName::File)
2221                                    .size(IconSize::XSmall)
2222                                    .color(Color::Disabled),
2223                            )
2224                            .child(
2225                                Label::new(label_text)
2226                                    .size(LabelSize::XSmall)
2227                                    .color(Color::Muted)
2228                                    .buffer_font(cx),
2229                            ),
2230                    )
2231                    .child(
2232                        IconButton::new("open-rule", IconName::ArrowUpRightAlt)
2233                            .shape(ui::IconButtonShape::Square)
2234                            .icon_size(IconSize::XSmall)
2235                            .icon_color(Color::Ignored)
2236                            .on_click(cx.listener(Self::handle_open_rules))
2237                            .tooltip(Tooltip::text("View Rules")),
2238                    ),
2239            )
2240            .into_any()
2241    }
2242
2243    fn handle_allow_tool(
2244        &mut self,
2245        tool_use_id: LanguageModelToolUseId,
2246        _: &ClickEvent,
2247        _window: &mut Window,
2248        cx: &mut Context<Self>,
2249    ) {
2250        if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
2251            .thread
2252            .read(cx)
2253            .pending_tool(&tool_use_id)
2254            .map(|tool_use| tool_use.status.clone())
2255        {
2256            self.thread.update(cx, |thread, cx| {
2257                thread.run_tool(
2258                    c.tool_use_id.clone(),
2259                    c.ui_text.clone(),
2260                    c.input.clone(),
2261                    &c.messages,
2262                    c.tool.clone(),
2263                    cx,
2264                );
2265            });
2266        }
2267    }
2268
2269    fn handle_deny_tool(
2270        &mut self,
2271        tool_use_id: LanguageModelToolUseId,
2272        tool_name: Arc<str>,
2273        _: &ClickEvent,
2274        _window: &mut Window,
2275        cx: &mut Context<Self>,
2276    ) {
2277        self.thread.update(cx, |thread, cx| {
2278            thread.deny_tool_use(tool_use_id, tool_name, cx);
2279        });
2280    }
2281
2282    fn handle_open_rules(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
2283        let Some(system_prompt_context) = self.thread.read(cx).system_prompt_context().as_ref()
2284        else {
2285            return;
2286        };
2287
2288        let abs_paths = system_prompt_context
2289            .worktrees
2290            .iter()
2291            .flat_map(|worktree| worktree.rules_file.as_ref())
2292            .map(|rules_file| rules_file.abs_path.to_path_buf())
2293            .collect::<Vec<_>>();
2294
2295        if let Ok(task) = self.workspace.update(cx, move |workspace, cx| {
2296            // TODO: Open a multibuffer instead? In some cases this doesn't make the set of rules
2297            // files clear. For example, if rules file 1 is already open but rules file 2 is not,
2298            // this would open and focus rules file 2 in a tab that is not next to rules file 1.
2299            workspace.open_paths(abs_paths, OpenOptions::default(), None, window, cx)
2300        }) {
2301            task.detach();
2302        }
2303    }
2304
2305    fn dismiss_notifications(&mut self, cx: &mut Context<ActiveThread>) {
2306        for window in self.notifications.drain(..) {
2307            window
2308                .update(cx, |_, window, _| {
2309                    window.remove_window();
2310                })
2311                .ok();
2312
2313            self.notification_subscriptions.remove(&window);
2314        }
2315    }
2316
2317    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
2318        if !self.show_scrollbar && !self.scrollbar_state.is_dragging() {
2319            return None;
2320        }
2321
2322        Some(
2323            div()
2324                .occlude()
2325                .id("active-thread-scrollbar")
2326                .on_mouse_move(cx.listener(|_, _, _, cx| {
2327                    cx.notify();
2328                    cx.stop_propagation()
2329                }))
2330                .on_hover(|_, _, cx| {
2331                    cx.stop_propagation();
2332                })
2333                .on_any_mouse_down(|_, _, cx| {
2334                    cx.stop_propagation();
2335                })
2336                .on_mouse_up(
2337                    MouseButton::Left,
2338                    cx.listener(|_, _, _, cx| {
2339                        cx.stop_propagation();
2340                    }),
2341                )
2342                .on_scroll_wheel(cx.listener(|_, _, _, cx| {
2343                    cx.notify();
2344                }))
2345                .h_full()
2346                .absolute()
2347                .right_1()
2348                .top_1()
2349                .bottom_0()
2350                .w(px(12.))
2351                .cursor_default()
2352                .children(Scrollbar::vertical(self.scrollbar_state.clone())),
2353        )
2354    }
2355
2356    fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
2357        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
2358        self.hide_scrollbar_task = Some(cx.spawn(async move |thread, cx| {
2359            cx.background_executor()
2360                .timer(SCROLLBAR_SHOW_INTERVAL)
2361                .await;
2362            thread
2363                .update(cx, |thread, cx| {
2364                    if !thread.scrollbar_state.is_dragging() {
2365                        thread.show_scrollbar = false;
2366                        cx.notify();
2367                    }
2368                })
2369                .log_err();
2370        }))
2371    }
2372}
2373
2374impl Render for ActiveThread {
2375    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2376        v_flex()
2377            .size_full()
2378            .relative()
2379            .on_mouse_move(cx.listener(|this, _, _, cx| {
2380                this.show_scrollbar = true;
2381                this.hide_scrollbar_later(cx);
2382                cx.notify();
2383            }))
2384            .on_scroll_wheel(cx.listener(|this, _, _, cx| {
2385                this.show_scrollbar = true;
2386                this.hide_scrollbar_later(cx);
2387                cx.notify();
2388            }))
2389            .on_mouse_up(
2390                MouseButton::Left,
2391                cx.listener(|this, _, _, cx| {
2392                    this.hide_scrollbar_later(cx);
2393                }),
2394            )
2395            .child(list(self.list_state.clone()).flex_grow())
2396            .when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
2397                this.child(scrollbar)
2398            })
2399    }
2400}
2401
2402pub(crate) fn open_context(
2403    id: ContextId,
2404    context_store: Entity<ContextStore>,
2405    workspace: Entity<Workspace>,
2406    window: &mut Window,
2407    cx: &mut App,
2408) {
2409    let Some(context) = context_store.read(cx).context_for_id(id) else {
2410        return;
2411    };
2412
2413    match context {
2414        AssistantContext::File(file_context) => {
2415            if let Some(project_path) = file_context.context_buffer.buffer.read(cx).project_path(cx)
2416            {
2417                workspace.update(cx, |workspace, cx| {
2418                    workspace
2419                        .open_path(project_path, None, true, window, cx)
2420                        .detach_and_log_err(cx);
2421                });
2422            }
2423        }
2424        AssistantContext::Directory(directory_context) => {
2425            let path = directory_context.project_path.clone();
2426            workspace.update(cx, |workspace, cx| {
2427                workspace.project().update(cx, |project, cx| {
2428                    if let Some(entry) = project.entry_for_path(&path, cx) {
2429                        cx.emit(project::Event::RevealInProjectPanel(entry.id));
2430                    }
2431                })
2432            })
2433        }
2434        AssistantContext::Symbol(symbol_context) => {
2435            if let Some(project_path) = symbol_context
2436                .context_symbol
2437                .buffer
2438                .read(cx)
2439                .project_path(cx)
2440            {
2441                let snapshot = symbol_context.context_symbol.buffer.read(cx).snapshot();
2442                let target_position = symbol_context
2443                    .context_symbol
2444                    .id
2445                    .range
2446                    .start
2447                    .to_point(&snapshot);
2448
2449                let open_task = workspace.update(cx, |workspace, cx| {
2450                    workspace.open_path(project_path, None, true, window, cx)
2451                });
2452                window
2453                    .spawn(cx, async move |cx| {
2454                        if let Some(active_editor) = open_task
2455                            .await
2456                            .log_err()
2457                            .and_then(|item| item.downcast::<Editor>())
2458                        {
2459                            active_editor
2460                                .downgrade()
2461                                .update_in(cx, |editor, window, cx| {
2462                                    editor.go_to_singleton_buffer_point(
2463                                        target_position,
2464                                        window,
2465                                        cx,
2466                                    );
2467                                })
2468                                .log_err();
2469                        }
2470                    })
2471                    .detach();
2472            }
2473        }
2474        AssistantContext::FetchedUrl(fetched_url_context) => {
2475            cx.open_url(&fetched_url_context.url);
2476        }
2477        AssistantContext::Thread(thread_context) => {
2478            let thread_id = thread_context.thread.read(cx).id().clone();
2479            workspace.update(cx, |workspace, cx| {
2480                if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
2481                    panel.update(cx, |panel, cx| {
2482                        panel
2483                            .open_thread(&thread_id, window, cx)
2484                            .detach_and_log_err(cx)
2485                    });
2486                }
2487            })
2488        }
2489    }
2490}