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 _, update_settings_file};
  28use std::rc::Rc;
  29use std::sync::Arc;
  30use std::time::Duration;
  31use text::ToPoint;
  32use theme::ThemeSettings;
  33use ui::{Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*};
  34use util::ResultExt as _;
  35use workspace::{OpenOptions, Workspace};
  36
  37use crate::context_store::{ContextStore, 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        let Some(editor) = self.feedback_comments_editor.clone() else {
 957            return;
 958        };
 959
 960        let report_task = self.thread.update(cx, |thread, cx| {
 961            thread.report_feedback(ThreadFeedback::Negative, cx)
 962        });
 963
 964        let comments = editor.read(cx).text(cx);
 965        if !comments.is_empty() {
 966            let thread_id = self.thread.read(cx).id().clone();
 967
 968            telemetry::event!("Assistant Thread Feedback Comments", thread_id, comments);
 969        }
 970
 971        self.showing_feedback_comments = false;
 972        self.feedback_comments_editor = None;
 973
 974        let this = cx.entity().downgrade();
 975        cx.spawn(async move |_, cx| {
 976            report_task.await?;
 977            this.update(cx, |_this, cx| cx.notify())
 978        })
 979        .detach_and_log_err(cx);
 980    }
 981
 982    fn handle_cancel_comments(
 983        &mut self,
 984        _: &ClickEvent,
 985        _window: &mut Window,
 986        cx: &mut Context<Self>,
 987    ) {
 988        self.showing_feedback_comments = false;
 989        self.feedback_comments_editor = None;
 990        cx.notify();
 991    }
 992
 993    fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
 994        let message_id = self.messages[ix];
 995        let Some(message) = self.thread.read(cx).message(message_id) else {
 996            return Empty.into_any();
 997        };
 998
 999        let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
1000            return Empty.into_any();
1001        };
1002
1003        let context_store = self.context_store.clone();
1004        let workspace = self.workspace.clone();
1005
1006        let thread = self.thread.read(cx);
1007        // Get all the data we need from thread before we start using it in closures
1008        let checkpoint = thread.checkpoint_for_message(message_id);
1009        let context = thread.context_for_message(message_id).collect::<Vec<_>>();
1010        let tool_uses = thread.tool_uses_for_message(message_id, cx);
1011        let has_tool_uses = !tool_uses.is_empty();
1012
1013        // Don't render user messages that are just there for returning tool results.
1014        if message.role == Role::User && thread.message_has_tool_results(message_id) {
1015            return Empty.into_any();
1016        }
1017
1018        let allow_editing_message =
1019            message.role == Role::User && self.last_user_message(cx) == Some(message_id);
1020
1021        let edit_message_editor = self
1022            .editing_message
1023            .as_ref()
1024            .filter(|(id, _)| *id == message_id)
1025            .map(|(_, state)| state.editor.clone());
1026
1027        let first_message = ix == 0;
1028        let show_feedback = ix == self.messages.len() - 1 && message.role != Role::User;
1029
1030        let colors = cx.theme().colors();
1031        let active_color = colors.element_active;
1032        let editor_bg_color = colors.editor_background;
1033        let bg_user_message_header = editor_bg_color.blend(active_color.opacity(0.25));
1034
1035        let feedback_container = h_flex().pt_2().pb_4().px_4().gap_1().justify_between();
1036        let feedback_items = match self.thread.read(cx).feedback() {
1037            Some(feedback) => feedback_container
1038                .child(
1039                    Label::new(match feedback {
1040                        ThreadFeedback::Positive => "Thanks for your feedback!",
1041                        ThreadFeedback::Negative => {
1042                            "We appreciate your feedback and will use it to improve."
1043                        }
1044                    })
1045                    .color(Color::Muted)
1046                    .size(LabelSize::XSmall),
1047                )
1048                .child(
1049                    h_flex()
1050                        .gap_1()
1051                        .child(
1052                            IconButton::new("feedback-thumbs-up", IconName::ThumbsUp)
1053                                .icon_size(IconSize::XSmall)
1054                                .icon_color(match feedback {
1055                                    ThreadFeedback::Positive => Color::Accent,
1056                                    ThreadFeedback::Negative => Color::Ignored,
1057                                })
1058                                .shape(ui::IconButtonShape::Square)
1059                                .tooltip(Tooltip::text("Helpful Response"))
1060                                .on_click(cx.listener(move |this, _, window, cx| {
1061                                    this.handle_feedback_click(
1062                                        ThreadFeedback::Positive,
1063                                        window,
1064                                        cx,
1065                                    );
1066                                })),
1067                        )
1068                        .child(
1069                            IconButton::new("feedback-thumbs-down", IconName::ThumbsDown)
1070                                .icon_size(IconSize::XSmall)
1071                                .icon_color(match feedback {
1072                                    ThreadFeedback::Positive => Color::Ignored,
1073                                    ThreadFeedback::Negative => Color::Accent,
1074                                })
1075                                .shape(ui::IconButtonShape::Square)
1076                                .tooltip(Tooltip::text("Not Helpful"))
1077                                .on_click(cx.listener(move |this, _, window, cx| {
1078                                    this.handle_feedback_click(
1079                                        ThreadFeedback::Negative,
1080                                        window,
1081                                        cx,
1082                                    );
1083                                })),
1084                        ),
1085                )
1086                .into_any_element(),
1087            None => feedback_container
1088                .child(
1089                    Label::new(
1090                        "Rating the thread sends all of your current conversation to the Zed team.",
1091                    )
1092                    .color(Color::Muted)
1093                    .size(LabelSize::XSmall),
1094                )
1095                .child(
1096                    h_flex()
1097                        .gap_1()
1098                        .child(
1099                            IconButton::new("feedback-thumbs-up", IconName::ThumbsUp)
1100                                .icon_size(IconSize::XSmall)
1101                                .icon_color(Color::Ignored)
1102                                .shape(ui::IconButtonShape::Square)
1103                                .tooltip(Tooltip::text("Helpful Response"))
1104                                .on_click(cx.listener(move |this, _, window, cx| {
1105                                    this.handle_feedback_click(
1106                                        ThreadFeedback::Positive,
1107                                        window,
1108                                        cx,
1109                                    );
1110                                })),
1111                        )
1112                        .child(
1113                            IconButton::new("feedback-thumbs-down", IconName::ThumbsDown)
1114                                .icon_size(IconSize::XSmall)
1115                                .icon_color(Color::Ignored)
1116                                .shape(ui::IconButtonShape::Square)
1117                                .tooltip(Tooltip::text("Not Helpful"))
1118                                .on_click(cx.listener(move |this, _, window, cx| {
1119                                    this.handle_feedback_click(
1120                                        ThreadFeedback::Negative,
1121                                        window,
1122                                        cx,
1123                                    );
1124                                })),
1125                        ),
1126                )
1127                .into_any_element(),
1128        };
1129
1130        let message_content = v_flex()
1131            .gap_1p5()
1132            .child(
1133                if let Some(edit_message_editor) = edit_message_editor.clone() {
1134                    div()
1135                        .key_context("EditMessageEditor")
1136                        .on_action(cx.listener(Self::cancel_editing_message))
1137                        .on_action(cx.listener(Self::confirm_editing_message))
1138                        .min_h_6()
1139                        .child(edit_message_editor)
1140                } else {
1141                    div()
1142                        .min_h_6()
1143                        .text_ui(cx)
1144                        .child(self.render_message_content(
1145                            message_id,
1146                            rendered_message,
1147                            has_tool_uses,
1148                            cx,
1149                        ))
1150                },
1151            )
1152            .when(!context.is_empty(), |parent| {
1153                parent.child(
1154                    h_flex()
1155                        .flex_wrap()
1156                        .gap_1()
1157                        .children(context.into_iter().map(|context| {
1158                            let context_id = context.id();
1159                            ContextPill::added(AddedContext::new(context, cx), false, false, None)
1160                                .on_click(Rc::new(cx.listener({
1161                                    let workspace = workspace.clone();
1162                                    let context_store = context_store.clone();
1163                                    move |_, _, window, cx| {
1164                                        if let Some(workspace) = workspace.upgrade() {
1165                                            open_context(
1166                                                context_id,
1167                                                context_store.clone(),
1168                                                workspace,
1169                                                window,
1170                                                cx,
1171                                            );
1172                                            cx.notify();
1173                                        }
1174                                    }
1175                                })))
1176                        })),
1177                )
1178            });
1179
1180        let styled_message = match message.role {
1181            Role::User => v_flex()
1182                .id(("message-container", ix))
1183                .map(|this| {
1184                    if first_message {
1185                        this.pt_2()
1186                    } else {
1187                        this.pt_4()
1188                    }
1189                })
1190                .pb_4()
1191                .pl_2()
1192                .pr_2p5()
1193                .child(
1194                    v_flex()
1195                        .bg(colors.editor_background)
1196                        .rounded_lg()
1197                        .border_1()
1198                        .border_color(colors.border)
1199                        .shadow_md()
1200                        .child(
1201                            h_flex()
1202                                .py_1()
1203                                .pl_2()
1204                                .pr_1()
1205                                .bg(bg_user_message_header)
1206                                .border_b_1()
1207                                .border_color(colors.border)
1208                                .justify_between()
1209                                .rounded_t_md()
1210                                .child(
1211                                    h_flex()
1212                                        .gap_1p5()
1213                                        .child(
1214                                            Icon::new(IconName::PersonCircle)
1215                                                .size(IconSize::XSmall)
1216                                                .color(Color::Muted),
1217                                        )
1218                                        .child(
1219                                            Label::new("You")
1220                                                .size(LabelSize::Small)
1221                                                .color(Color::Muted),
1222                                        ),
1223                                )
1224                                .child(
1225                                    h_flex()
1226                                        // DL: To double-check whether we want to fully remove
1227                                        // the editing feature from meassages. Checkpoint sort of
1228                                        // solve the same problem.
1229                                        .invisible()
1230                                        .gap_1()
1231                                        .when_some(
1232                                            edit_message_editor.clone(),
1233                                            |this, edit_message_editor| {
1234                                                let focus_handle =
1235                                                    edit_message_editor.focus_handle(cx);
1236                                                this.child(
1237                                                    Button::new("cancel-edit-message", "Cancel")
1238                                                        .label_size(LabelSize::Small)
1239                                                        .key_binding(
1240                                                            KeyBinding::for_action_in(
1241                                                                &menu::Cancel,
1242                                                                &focus_handle,
1243                                                                window,
1244                                                                cx,
1245                                                            )
1246                                                            .map(|kb| kb.size(rems_from_px(12.))),
1247                                                        )
1248                                                        .on_click(
1249                                                            cx.listener(Self::handle_cancel_click),
1250                                                        ),
1251                                                )
1252                                                .child(
1253                                                    Button::new(
1254                                                        "confirm-edit-message",
1255                                                        "Regenerate",
1256                                                    )
1257                                                    .label_size(LabelSize::Small)
1258                                                    .key_binding(
1259                                                        KeyBinding::for_action_in(
1260                                                            &menu::Confirm,
1261                                                            &focus_handle,
1262                                                            window,
1263                                                            cx,
1264                                                        )
1265                                                        .map(|kb| kb.size(rems_from_px(12.))),
1266                                                    )
1267                                                    .on_click(
1268                                                        cx.listener(Self::handle_regenerate_click),
1269                                                    ),
1270                                                )
1271                                            },
1272                                        )
1273                                        .when(
1274                                            edit_message_editor.is_none() && allow_editing_message,
1275                                            |this| {
1276                                                this.child(
1277                                                    Button::new("edit-message", "Edit")
1278                                                        .label_size(LabelSize::Small)
1279                                                        .on_click(cx.listener({
1280                                                            let message_segments =
1281                                                                message.segments.clone();
1282                                                            move |this, _, window, cx| {
1283                                                                this.start_editing_message(
1284                                                                    message_id,
1285                                                                    &message_segments,
1286                                                                    window,
1287                                                                    cx,
1288                                                                );
1289                                                            }
1290                                                        })),
1291                                                )
1292                                            },
1293                                        ),
1294                                ),
1295                        )
1296                        .child(div().p_2().child(message_content)),
1297                ),
1298            Role::Assistant => v_flex()
1299                .id(("message-container", ix))
1300                .ml_2()
1301                .pl_2()
1302                .pr_4()
1303                .border_l_1()
1304                .border_color(cx.theme().colors().border_variant)
1305                .child(message_content)
1306                .when(!tool_uses.is_empty(), |parent| {
1307                    parent.child(
1308                        v_flex().children(
1309                            tool_uses
1310                                .into_iter()
1311                                .map(|tool_use| self.render_tool_use(tool_use, cx)),
1312                        ),
1313                    )
1314                }),
1315            Role::System => div().id(("message-container", ix)).py_1().px_2().child(
1316                v_flex()
1317                    .bg(colors.editor_background)
1318                    .rounded_sm()
1319                    .child(div().p_4().child(message_content)),
1320            ),
1321        };
1322
1323        v_flex()
1324            .w_full()
1325            .when(first_message, |parent| {
1326                parent.child(self.render_rules_item(cx))
1327            })
1328            .when_some(checkpoint, |parent, checkpoint| {
1329                let mut is_pending = false;
1330                let mut error = None;
1331                if let Some(last_restore_checkpoint) =
1332                    self.thread.read(cx).last_restore_checkpoint()
1333                {
1334                    if last_restore_checkpoint.message_id() == message_id {
1335                        match last_restore_checkpoint {
1336                            LastRestoreCheckpoint::Pending { .. } => is_pending = true,
1337                            LastRestoreCheckpoint::Error { error: err, .. } => {
1338                                error = Some(err.clone());
1339                            }
1340                        }
1341                    }
1342                }
1343
1344                let restore_checkpoint_button =
1345                    Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
1346                        .icon(if error.is_some() {
1347                            IconName::XCircle
1348                        } else {
1349                            IconName::Undo
1350                        })
1351                        .icon_size(IconSize::XSmall)
1352                        .icon_position(IconPosition::Start)
1353                        .icon_color(if error.is_some() {
1354                            Some(Color::Error)
1355                        } else {
1356                            None
1357                        })
1358                        .label_size(LabelSize::XSmall)
1359                        .disabled(is_pending)
1360                        .on_click(cx.listener(move |this, _, _window, cx| {
1361                            this.thread.update(cx, |thread, cx| {
1362                                thread
1363                                    .restore_checkpoint(checkpoint.clone(), cx)
1364                                    .detach_and_log_err(cx);
1365                            });
1366                        }));
1367
1368                let restore_checkpoint_button = if is_pending {
1369                    restore_checkpoint_button
1370                        .with_animation(
1371                            ("pulsating-restore-checkpoint-button", ix),
1372                            Animation::new(Duration::from_secs(2))
1373                                .repeat()
1374                                .with_easing(pulsating_between(0.6, 1.)),
1375                            |label, delta| label.alpha(delta),
1376                        )
1377                        .into_any_element()
1378                } else if let Some(error) = error {
1379                    restore_checkpoint_button
1380                        .tooltip(Tooltip::text(error.to_string()))
1381                        .into_any_element()
1382                } else {
1383                    restore_checkpoint_button.into_any_element()
1384                };
1385
1386                parent.child(
1387                    h_flex()
1388                        .pt_2p5()
1389                        .px_2p5()
1390                        .w_full()
1391                        .gap_1()
1392                        .child(ui::Divider::horizontal())
1393                        .child(restore_checkpoint_button)
1394                        .child(ui::Divider::horizontal()),
1395                )
1396            })
1397            .child(styled_message)
1398            .when(
1399                show_feedback && !self.thread.read(cx).is_generating(),
1400                |parent| {
1401                    parent
1402                        .child(feedback_items)
1403                        .when(self.showing_feedback_comments, |parent| {
1404                            parent.child(
1405                                v_flex()
1406                                    .gap_1()
1407                                    .px_4()
1408                                    .child(
1409                                        Label::new(
1410                                            "Please share your feedback to help us improve:",
1411                                        )
1412                                        .size(LabelSize::Small),
1413                                    )
1414                                    .child(
1415                                        div()
1416                                            .p_2()
1417                                            .rounded_md()
1418                                            .border_1()
1419                                            .border_color(cx.theme().colors().border)
1420                                            .bg(cx.theme().colors().editor_background)
1421                                            .child(
1422                                                self.feedback_comments_editor
1423                                                    .as_ref()
1424                                                    .unwrap()
1425                                                    .clone(),
1426                                            ),
1427                                    )
1428                                    .child(
1429                                        h_flex()
1430                                            .gap_1()
1431                                            .justify_end()
1432                                            .pb_2()
1433                                            .child(
1434                                                Button::new("cancel-comments", "Cancel").on_click(
1435                                                    cx.listener(Self::handle_cancel_comments),
1436                                                ),
1437                                            )
1438                                            .child(
1439                                                Button::new("submit-comments", "Submit").on_click(
1440                                                    cx.listener(Self::handle_submit_comments),
1441                                                ),
1442                                            ),
1443                                    ),
1444                            )
1445                        })
1446                },
1447            )
1448            .into_any()
1449    }
1450
1451    fn render_message_content(
1452        &self,
1453        message_id: MessageId,
1454        rendered_message: &RenderedMessage,
1455        has_tool_uses: bool,
1456        cx: &Context<Self>,
1457    ) -> impl IntoElement {
1458        let is_last_message = self.messages.last() == Some(&message_id);
1459        let pending_thinking_segment_index = if is_last_message && !has_tool_uses {
1460            rendered_message
1461                .segments
1462                .iter()
1463                .enumerate()
1464                .last()
1465                .filter(|(_, segment)| matches!(segment, RenderedMessageSegment::Thinking { .. }))
1466                .map(|(index, _)| index)
1467        } else {
1468            None
1469        };
1470
1471        div()
1472            .text_ui(cx)
1473            .gap_2()
1474            .children(
1475                rendered_message.segments.iter().enumerate().map(
1476                    |(index, segment)| match segment {
1477                        RenderedMessageSegment::Thinking {
1478                            content,
1479                            scroll_handle,
1480                        } => self
1481                            .render_message_thinking_segment(
1482                                message_id,
1483                                index,
1484                                content.clone(),
1485                                &scroll_handle,
1486                                Some(index) == pending_thinking_segment_index,
1487                                cx,
1488                            )
1489                            .into_any_element(),
1490                        RenderedMessageSegment::Text(markdown) => {
1491                            div().child(markdown.clone()).into_any_element()
1492                        }
1493                    },
1494                ),
1495            )
1496    }
1497
1498    fn tool_card_border_color(&self, cx: &Context<Self>) -> Hsla {
1499        cx.theme().colors().border.opacity(0.5)
1500    }
1501
1502    fn tool_card_header_bg(&self, cx: &Context<Self>) -> Hsla {
1503        cx.theme()
1504            .colors()
1505            .element_background
1506            .blend(cx.theme().colors().editor_foreground.opacity(0.025))
1507    }
1508
1509    fn render_message_thinking_segment(
1510        &self,
1511        message_id: MessageId,
1512        ix: usize,
1513        markdown: Entity<Markdown>,
1514        scroll_handle: &ScrollHandle,
1515        pending: bool,
1516        cx: &Context<Self>,
1517    ) -> impl IntoElement {
1518        let is_open = self
1519            .expanded_thinking_segments
1520            .get(&(message_id, ix))
1521            .copied()
1522            .unwrap_or_default();
1523
1524        let editor_bg = cx.theme().colors().editor_background;
1525
1526        div().pt_0p5().pb_2().child(
1527            v_flex()
1528                .rounded_lg()
1529                .border_1()
1530                .border_color(self.tool_card_border_color(cx))
1531                .child(
1532                    h_flex()
1533                        .group("disclosure-header")
1534                        .justify_between()
1535                        .py_1()
1536                        .px_2()
1537                        .bg(self.tool_card_header_bg(cx))
1538                        .map(|this| {
1539                            if pending || is_open {
1540                                this.rounded_t_md()
1541                                    .border_b_1()
1542                                    .border_color(self.tool_card_border_color(cx))
1543                            } else {
1544                                this.rounded_md()
1545                            }
1546                        })
1547                        .child(
1548                            h_flex()
1549                                .gap_1p5()
1550                                .child(
1551                                    Icon::new(IconName::Brain)
1552                                        .size(IconSize::XSmall)
1553                                        .color(Color::Muted),
1554                                )
1555                                .child({
1556                                    if pending {
1557                                        Label::new("Thinking…")
1558                                            .size(LabelSize::Small)
1559                                            .buffer_font(cx)
1560                                            .with_animation(
1561                                                "pulsating-label",
1562                                                Animation::new(Duration::from_secs(2))
1563                                                    .repeat()
1564                                                    .with_easing(pulsating_between(0.4, 0.8)),
1565                                                |label, delta| label.alpha(delta),
1566                                            )
1567                                            .into_any_element()
1568                                    } else {
1569                                        Label::new("Thought Process")
1570                                            .size(LabelSize::Small)
1571                                            .buffer_font(cx)
1572                                            .into_any_element()
1573                                    }
1574                                }),
1575                        )
1576                        .child(
1577                            h_flex()
1578                                .gap_1()
1579                                .child(
1580                                    div().visible_on_hover("disclosure-header").child(
1581                                        Disclosure::new("thinking-disclosure", is_open)
1582                                            .opened_icon(IconName::ChevronUp)
1583                                            .closed_icon(IconName::ChevronDown)
1584                                            .on_click(cx.listener({
1585                                                move |this, _event, _window, _cx| {
1586                                                    let is_open = this
1587                                                        .expanded_thinking_segments
1588                                                        .entry((message_id, ix))
1589                                                        .or_insert(false);
1590
1591                                                    *is_open = !*is_open;
1592                                                }
1593                                            })),
1594                                    ),
1595                                )
1596                                .child({
1597                                    let (icon_name, color, animated) = if pending {
1598                                        (IconName::ArrowCircle, Color::Accent, true)
1599                                    } else {
1600                                        (IconName::Check, Color::Success, false)
1601                                    };
1602
1603                                    let icon =
1604                                        Icon::new(icon_name).color(color).size(IconSize::Small);
1605
1606                                    if animated {
1607                                        icon.with_animation(
1608                                            "arrow-circle",
1609                                            Animation::new(Duration::from_secs(2)).repeat(),
1610                                            |icon, delta| {
1611                                                icon.transform(Transformation::rotate(percentage(
1612                                                    delta,
1613                                                )))
1614                                            },
1615                                        )
1616                                        .into_any_element()
1617                                    } else {
1618                                        icon.into_any_element()
1619                                    }
1620                                }),
1621                        ),
1622                )
1623                .when(pending && !is_open, |this| {
1624                    let gradient_overlay = div()
1625                        .rounded_b_lg()
1626                        .h_20()
1627                        .absolute()
1628                        .w_full()
1629                        .bottom_0()
1630                        .left_0()
1631                        .bg(linear_gradient(
1632                            180.,
1633                            linear_color_stop(editor_bg, 1.),
1634                            linear_color_stop(editor_bg.opacity(0.2), 0.),
1635                        ));
1636
1637                    this.child(
1638                        div()
1639                            .relative()
1640                            .bg(editor_bg)
1641                            .rounded_b_lg()
1642                            .child(
1643                                div()
1644                                    .id(("thinking-content", ix))
1645                                    .p_2()
1646                                    .h_20()
1647                                    .track_scroll(scroll_handle)
1648                                    .text_ui_sm(cx)
1649                                    .child(markdown.clone())
1650                                    .overflow_hidden(),
1651                            )
1652                            .child(gradient_overlay),
1653                    )
1654                })
1655                .when(is_open, |this| {
1656                    this.child(
1657                        div()
1658                            .id(("thinking-content", ix))
1659                            .h_full()
1660                            .p_2()
1661                            .rounded_b_lg()
1662                            .bg(editor_bg)
1663                            .text_ui_sm(cx)
1664                            .child(markdown.clone()),
1665                    )
1666                }),
1667        )
1668    }
1669
1670    fn render_tool_use(
1671        &self,
1672        tool_use: ToolUse,
1673        cx: &mut Context<Self>,
1674    ) -> impl IntoElement + use<> {
1675        let is_open = self
1676            .expanded_tool_uses
1677            .get(&tool_use.id)
1678            .copied()
1679            .unwrap_or_default();
1680
1681        let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_));
1682
1683        let fs = self
1684            .workspace
1685            .upgrade()
1686            .map(|workspace| workspace.read(cx).app_state().fs.clone());
1687        let needs_confirmation = matches!(&tool_use.status, ToolUseStatus::NeedsConfirmation);
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(px(46.))
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                                .justify_between()
1908                                .py_1()
1909                                .map(|element| {
1910                                    if is_status_finished {
1911                                        element.pl_2().pr_0p5()
1912                                    } else {
1913                                        element.px_2()
1914                                    }
1915                                })
1916                                .bg(self.tool_card_header_bg(cx))
1917                                .map(|element| {
1918                                    if is_open {
1919                                        element.border_b_1().rounded_t_md()
1920                                    } else if needs_confirmation {
1921                                        element.rounded_t_md()
1922                                    } else {
1923                                        element.rounded_md()
1924                                    }
1925                                })
1926                                .border_color(self.tool_card_border_color(cx))
1927                                .child(
1928                                    h_flex()
1929                                        .id("tool-label-container")
1930                                        .gap_1p5()
1931                                        .max_w_full()
1932                                        .overflow_x_scroll()
1933                                        .child(
1934                                            Icon::new(tool_use.icon)
1935                                                .size(IconSize::XSmall)
1936                                                .color(Color::Muted),
1937                                        )
1938                                        .child(
1939                                            h_flex().pr_8().text_ui_sm(cx).children(
1940                                                self.rendered_tool_use_labels
1941                                                    .get(&tool_use.id)
1942                                                    .cloned(),
1943                                            ),
1944                                        ),
1945                                )
1946                                .child(
1947                                    h_flex()
1948                                        .gap_1()
1949                                        .child(
1950                                            div().visible_on_hover("disclosure-header").child(
1951                                                Disclosure::new("tool-use-disclosure", is_open)
1952                                                    .opened_icon(IconName::ChevronUp)
1953                                                    .closed_icon(IconName::ChevronDown)
1954                                                    .on_click(cx.listener({
1955                                                        let tool_use_id = tool_use.id.clone();
1956                                                        move |this, _event, _window, _cx| {
1957                                                            let is_open = this
1958                                                                .expanded_tool_uses
1959                                                                .entry(tool_use_id.clone())
1960                                                                .or_insert(false);
1961
1962                                                            *is_open = !*is_open;
1963                                                        }
1964                                                    })),
1965                                            ),
1966                                        )
1967                                        .child(status_icons),
1968                                )
1969                                .child(gradient_overlay(self.tool_card_header_bg(cx))),
1970                        )
1971                        .map(|parent| {
1972                            if !is_open {
1973                                return parent;
1974                            }
1975
1976                            parent.child(
1977                                v_flex()
1978                                    .bg(cx.theme().colors().editor_background)
1979                                    .map(|element| {
1980                                        if  needs_confirmation {
1981                                            element.rounded_none()
1982                                        } else {
1983                                            element.rounded_b_lg()
1984                                        }
1985                                    })
1986                                    .child(results_content),
1987                            )
1988                        })
1989                        .when(needs_confirmation, |this| {
1990                            this.child(
1991                                h_flex()
1992                                    .py_1()
1993                                    .pl_2()
1994                                    .pr_1()
1995                                    .gap_1()
1996                                    .justify_between()
1997                                    .bg(cx.theme().colors().editor_background)
1998                                    .border_t_1()
1999                                    .border_color(self.tool_card_border_color(cx))
2000                                    .rounded_b_lg()
2001                                    .child(Label::new("Action Confirmation").color(Color::Muted).size(LabelSize::Small))
2002                                    .child(
2003                                        h_flex()
2004                                            .gap_0p5()
2005                                            .child({
2006                                                let tool_id = tool_use.id.clone();
2007                                                Button::new(
2008                                                    "always-allow-tool-action",
2009                                                    "Always Allow",
2010                                                )
2011                                                .label_size(LabelSize::Small)
2012                                                .icon(IconName::CheckDouble)
2013                                                .icon_position(IconPosition::Start)
2014                                                .icon_size(IconSize::Small)
2015                                                .icon_color(Color::Success)
2016                                                .tooltip(move |window, cx|  {
2017                                                    Tooltip::with_meta(
2018                                                        "Never ask for permission",
2019                                                        None,
2020                                                        "Restore the original behavior in your Agent Panel settings",
2021                                                        window,
2022                                                        cx,
2023                                                    )
2024                                                })
2025                                                .on_click(cx.listener(
2026                                                    move |this, event, window, cx| {
2027                                                        if let Some(fs) = fs.clone() {
2028                                                            update_settings_file::<AssistantSettings>(
2029                                                                fs.clone(),
2030                                                                cx,
2031                                                                |settings, _| {
2032                                                                    settings.set_always_allow_tool_actions(true);
2033                                                                },
2034                                                            );
2035                                                        }
2036                                                        this.handle_allow_tool(
2037                                                            tool_id.clone(),
2038                                                            event,
2039                                                            window,
2040                                                            cx,
2041                                                        )
2042                                                    },
2043                                                ))
2044                                            })
2045                                            .child(ui::Divider::vertical())
2046                                            .child({
2047                                                let tool_id = tool_use.id.clone();
2048                                                Button::new("allow-tool-action", "Allow")
2049                                                    .label_size(LabelSize::Small)
2050                                                    .icon(IconName::Check)
2051                                                    .icon_position(IconPosition::Start)
2052                                                    .icon_size(IconSize::Small)
2053                                                    .icon_color(Color::Success)
2054                                                    .on_click(cx.listener(
2055                                                        move |this, event, window, cx| {
2056                                                            this.handle_allow_tool(
2057                                                                tool_id.clone(),
2058                                                                event,
2059                                                                window,
2060                                                                cx,
2061                                                            )
2062                                                        },
2063                                                    ))
2064                                            })
2065                                            .child({
2066                                                let tool_id = tool_use.id.clone();
2067                                                let tool_name: Arc<str> = tool_use.name.into();
2068                                                Button::new("deny-tool", "Deny")
2069                                                    .label_size(LabelSize::Small)
2070                                                    .icon(IconName::Close)
2071                                                    .icon_position(IconPosition::Start)
2072                                                    .icon_size(IconSize::Small)
2073                                                    .icon_color(Color::Error)
2074                                                    .on_click(cx.listener(
2075                                                        move |this, event, window, cx| {
2076                                                            this.handle_deny_tool(
2077                                                                tool_id.clone(),
2078                                                                tool_name.clone(),
2079                                                                event,
2080                                                                window,
2081                                                                cx,
2082                                                            )
2083                                                        },
2084                                                    ))
2085                                            }),
2086                                    ),
2087                            )
2088                        }),
2089                )
2090            }
2091        })
2092    }
2093
2094    fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
2095        let Some(system_prompt_context) = self.thread.read(cx).system_prompt_context().as_ref()
2096        else {
2097            return div().into_any();
2098        };
2099
2100        let rules_files = system_prompt_context
2101            .worktrees
2102            .iter()
2103            .filter_map(|worktree| worktree.rules_file.as_ref())
2104            .collect::<Vec<_>>();
2105
2106        let label_text = match rules_files.as_slice() {
2107            &[] => return div().into_any(),
2108            &[rules_file] => {
2109                format!("Using {:?} file", rules_file.rel_path)
2110            }
2111            rules_files => {
2112                format!("Using {} rules files", rules_files.len())
2113            }
2114        };
2115
2116        div()
2117            .pt_1()
2118            .px_2p5()
2119            .child(
2120                h_flex()
2121                    .w_full()
2122                    .gap_0p5()
2123                    .child(
2124                        h_flex()
2125                            .gap_1p5()
2126                            .child(
2127                                Icon::new(IconName::File)
2128                                    .size(IconSize::XSmall)
2129                                    .color(Color::Disabled),
2130                            )
2131                            .child(
2132                                Label::new(label_text)
2133                                    .size(LabelSize::XSmall)
2134                                    .color(Color::Muted)
2135                                    .buffer_font(cx),
2136                            ),
2137                    )
2138                    .child(
2139                        IconButton::new("open-rule", IconName::ArrowUpRightAlt)
2140                            .shape(ui::IconButtonShape::Square)
2141                            .icon_size(IconSize::XSmall)
2142                            .icon_color(Color::Ignored)
2143                            .on_click(cx.listener(Self::handle_open_rules))
2144                            .tooltip(Tooltip::text("View Rules")),
2145                    ),
2146            )
2147            .into_any()
2148    }
2149
2150    fn handle_allow_tool(
2151        &mut self,
2152        tool_use_id: LanguageModelToolUseId,
2153        _: &ClickEvent,
2154        _window: &mut Window,
2155        cx: &mut Context<Self>,
2156    ) {
2157        if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
2158            .thread
2159            .read(cx)
2160            .pending_tool(&tool_use_id)
2161            .map(|tool_use| tool_use.status.clone())
2162        {
2163            self.thread.update(cx, |thread, cx| {
2164                thread.run_tool(
2165                    c.tool_use_id.clone(),
2166                    c.ui_text.clone(),
2167                    c.input.clone(),
2168                    &c.messages,
2169                    c.tool.clone(),
2170                    cx,
2171                );
2172            });
2173        }
2174    }
2175
2176    fn handle_deny_tool(
2177        &mut self,
2178        tool_use_id: LanguageModelToolUseId,
2179        tool_name: Arc<str>,
2180        _: &ClickEvent,
2181        _window: &mut Window,
2182        cx: &mut Context<Self>,
2183    ) {
2184        self.thread.update(cx, |thread, cx| {
2185            thread.deny_tool_use(tool_use_id, tool_name, cx);
2186        });
2187    }
2188
2189    fn handle_open_rules(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
2190        let Some(system_prompt_context) = self.thread.read(cx).system_prompt_context().as_ref()
2191        else {
2192            return;
2193        };
2194
2195        let abs_paths = system_prompt_context
2196            .worktrees
2197            .iter()
2198            .flat_map(|worktree| worktree.rules_file.as_ref())
2199            .map(|rules_file| rules_file.abs_path.to_path_buf())
2200            .collect::<Vec<_>>();
2201
2202        if let Ok(task) = self.workspace.update(cx, move |workspace, cx| {
2203            // TODO: Open a multibuffer instead? In some cases this doesn't make the set of rules
2204            // files clear. For example, if rules file 1 is already open but rules file 2 is not,
2205            // this would open and focus rules file 2 in a tab that is not next to rules file 1.
2206            workspace.open_paths(abs_paths, OpenOptions::default(), None, window, cx)
2207        }) {
2208            task.detach();
2209        }
2210    }
2211
2212    fn dismiss_notifications(&mut self, cx: &mut Context<ActiveThread>) {
2213        for window in self.notifications.drain(..) {
2214            window
2215                .update(cx, |_, window, _| {
2216                    window.remove_window();
2217                })
2218                .ok();
2219
2220            self.notification_subscriptions.remove(&window);
2221        }
2222    }
2223
2224    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
2225        div()
2226            .occlude()
2227            .id("active-thread-scrollbar")
2228            .on_mouse_move(cx.listener(|_, _, _, cx| {
2229                cx.notify();
2230                cx.stop_propagation()
2231            }))
2232            .on_hover(|_, _, cx| {
2233                cx.stop_propagation();
2234            })
2235            .on_any_mouse_down(|_, _, cx| {
2236                cx.stop_propagation();
2237            })
2238            .on_mouse_up(
2239                MouseButton::Left,
2240                cx.listener(|_, _, _, cx| {
2241                    cx.stop_propagation();
2242                }),
2243            )
2244            .on_scroll_wheel(cx.listener(|_, _, _, cx| {
2245                cx.notify();
2246            }))
2247            .h_full()
2248            .absolute()
2249            .right_1()
2250            .top_1()
2251            .bottom_0()
2252            .w(px(12.))
2253            .cursor_default()
2254            .children(Scrollbar::vertical(self.scrollbar_state.clone()))
2255    }
2256}
2257
2258impl Render for ActiveThread {
2259    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2260        v_flex()
2261            .size_full()
2262            .relative()
2263            .child(list(self.list_state.clone()).flex_grow())
2264            .child(self.render_vertical_scrollbar(cx))
2265    }
2266}
2267
2268pub(crate) fn open_context(
2269    id: ContextId,
2270    context_store: Entity<ContextStore>,
2271    workspace: Entity<Workspace>,
2272    window: &mut Window,
2273    cx: &mut App,
2274) {
2275    let Some(context) = context_store.read(cx).context_for_id(id) else {
2276        return;
2277    };
2278
2279    match context {
2280        AssistantContext::File(file_context) => {
2281            if let Some(project_path) = file_context.context_buffer.buffer.read(cx).project_path(cx)
2282            {
2283                workspace.update(cx, |workspace, cx| {
2284                    workspace
2285                        .open_path(project_path, None, true, window, cx)
2286                        .detach_and_log_err(cx);
2287                });
2288            }
2289        }
2290        AssistantContext::Directory(directory_context) => {
2291            let path = directory_context.project_path.clone();
2292            workspace.update(cx, |workspace, cx| {
2293                workspace.project().update(cx, |project, cx| {
2294                    if let Some(entry) = project.entry_for_path(&path, cx) {
2295                        cx.emit(project::Event::RevealInProjectPanel(entry.id));
2296                    }
2297                })
2298            })
2299        }
2300        AssistantContext::Symbol(symbol_context) => {
2301            if let Some(project_path) = symbol_context
2302                .context_symbol
2303                .buffer
2304                .read(cx)
2305                .project_path(cx)
2306            {
2307                let snapshot = symbol_context.context_symbol.buffer.read(cx).snapshot();
2308                let target_position = symbol_context
2309                    .context_symbol
2310                    .id
2311                    .range
2312                    .start
2313                    .to_point(&snapshot);
2314
2315                let open_task = workspace.update(cx, |workspace, cx| {
2316                    workspace.open_path(project_path, None, true, window, cx)
2317                });
2318                window
2319                    .spawn(cx, async move |cx| {
2320                        if let Some(active_editor) = open_task
2321                            .await
2322                            .log_err()
2323                            .and_then(|item| item.downcast::<Editor>())
2324                        {
2325                            active_editor
2326                                .downgrade()
2327                                .update_in(cx, |editor, window, cx| {
2328                                    editor.go_to_singleton_buffer_point(
2329                                        target_position,
2330                                        window,
2331                                        cx,
2332                                    );
2333                                })
2334                                .log_err();
2335                        }
2336                    })
2337                    .detach();
2338            }
2339        }
2340        AssistantContext::FetchedUrl(fetched_url_context) => {
2341            cx.open_url(&fetched_url_context.url);
2342        }
2343        AssistantContext::Thread(thread_context) => {
2344            let thread_id = thread_context.thread.read(cx).id().clone();
2345            workspace.update(cx, |workspace, cx| {
2346                if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
2347                    panel.update(cx, |panel, cx| {
2348                        panel
2349                            .open_thread(&thread_id, window, cx)
2350                            .detach_and_log_err(cx)
2351                    });
2352                }
2353            })
2354        }
2355    }
2356}