active_thread.rs

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