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