active_thread.rs

   1use crate::thread::{
   2    LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError,
   3    ThreadEvent, ThreadFeedback,
   4};
   5use crate::thread_store::ThreadStore;
   6use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus};
   7use crate::ui::ContextPill;
   8
   9use collections::HashMap;
  10use editor::{Editor, MultiBuffer};
  11use gpui::{
  12    linear_color_stop, linear_gradient, list, percentage, pulsating_between, AbsoluteLength,
  13    Animation, AnimationExt, AnyElement, App, ClickEvent, DefiniteLength, EdgesRefinement, Empty,
  14    Entity, Focusable, Length, ListAlignment, ListOffset, ListState, ScrollHandle, StyleRefinement,
  15    Subscription, Task, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity,
  16};
  17use language::{Buffer, LanguageRegistry};
  18use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
  19use markdown::{Markdown, MarkdownStyle};
  20use settings::Settings as _;
  21use std::sync::Arc;
  22use std::time::Duration;
  23use theme::ThemeSettings;
  24use ui::{prelude::*, Disclosure, IconButton, KeyBinding, Tooltip};
  25use util::ResultExt as _;
  26use workspace::{OpenOptions, Workspace};
  27
  28use crate::context_store::{refresh_context_store_text, ContextStore};
  29
  30pub struct ActiveThread {
  31    language_registry: Arc<LanguageRegistry>,
  32    thread_store: Entity<ThreadStore>,
  33    thread: Entity<Thread>,
  34    context_store: Entity<ContextStore>,
  35    workspace: WeakEntity<Workspace>,
  36    save_thread_task: Option<Task<()>>,
  37    messages: Vec<MessageId>,
  38    list_state: ListState,
  39    rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
  40    rendered_tool_use_labels: HashMap<LanguageModelToolUseId, Entity<Markdown>>,
  41    editing_message: Option<(MessageId, EditMessageState)>,
  42    expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
  43    expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
  44    last_error: Option<ThreadError>,
  45    _subscriptions: Vec<Subscription>,
  46}
  47
  48struct RenderedMessage {
  49    language_registry: Arc<LanguageRegistry>,
  50    segments: Vec<RenderedMessageSegment>,
  51}
  52
  53impl RenderedMessage {
  54    fn from_segments(
  55        segments: &[MessageSegment],
  56        language_registry: Arc<LanguageRegistry>,
  57        window: &Window,
  58        cx: &mut App,
  59    ) -> Self {
  60        let mut this = Self {
  61            language_registry,
  62            segments: Vec::with_capacity(segments.len()),
  63        };
  64        for segment in segments {
  65            this.push_segment(segment, window, cx);
  66        }
  67        this
  68    }
  69
  70    fn append_thinking(&mut self, text: &String, window: &Window, cx: &mut App) {
  71        if let Some(RenderedMessageSegment::Thinking {
  72            content,
  73            scroll_handle,
  74        }) = self.segments.last_mut()
  75        {
  76            content.update(cx, |markdown, cx| {
  77                markdown.append(text, cx);
  78            });
  79            scroll_handle.scroll_to_bottom();
  80        } else {
  81            self.segments.push(RenderedMessageSegment::Thinking {
  82                content: render_markdown(text.into(), self.language_registry.clone(), window, cx),
  83                scroll_handle: ScrollHandle::default(),
  84            });
  85        }
  86    }
  87
  88    fn append_text(&mut self, text: &String, window: &Window, cx: &mut App) {
  89        if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() {
  90            markdown.update(cx, |markdown, cx| markdown.append(text, cx));
  91        } else {
  92            self.segments
  93                .push(RenderedMessageSegment::Text(render_markdown(
  94                    SharedString::from(text),
  95                    self.language_registry.clone(),
  96                    window,
  97                    cx,
  98                )));
  99        }
 100    }
 101
 102    fn push_segment(&mut self, segment: &MessageSegment, window: &Window, cx: &mut App) {
 103        let rendered_segment = match segment {
 104            MessageSegment::Thinking(text) => RenderedMessageSegment::Thinking {
 105                content: render_markdown(text.into(), self.language_registry.clone(), window, cx),
 106                scroll_handle: ScrollHandle::default(),
 107            },
 108            MessageSegment::Text(text) => RenderedMessageSegment::Text(render_markdown(
 109                text.into(),
 110                self.language_registry.clone(),
 111                window,
 112                cx,
 113            )),
 114        };
 115        self.segments.push(rendered_segment);
 116    }
 117}
 118
 119enum RenderedMessageSegment {
 120    Thinking {
 121        content: Entity<Markdown>,
 122        scroll_handle: ScrollHandle,
 123    },
 124    Text(Entity<Markdown>),
 125}
 126
 127fn render_markdown(
 128    text: SharedString,
 129    language_registry: Arc<LanguageRegistry>,
 130    window: &Window,
 131    cx: &mut App,
 132) -> Entity<Markdown> {
 133    let theme_settings = ThemeSettings::get_global(cx);
 134    let colors = cx.theme().colors();
 135    let ui_font_size = TextSize::Default.rems(cx);
 136    let buffer_font_size = TextSize::Small.rems(cx);
 137    let mut text_style = window.text_style();
 138
 139    text_style.refine(&TextStyleRefinement {
 140        font_family: Some(theme_settings.ui_font.family.clone()),
 141        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
 142        font_features: Some(theme_settings.ui_font.features.clone()),
 143        font_size: Some(ui_font_size.into()),
 144        color: Some(cx.theme().colors().text),
 145        ..Default::default()
 146    });
 147
 148    let markdown_style = MarkdownStyle {
 149        base_text_style: text_style,
 150        syntax: cx.theme().syntax().clone(),
 151        selection_background_color: cx.theme().players().local().selection,
 152        code_block_overflow_x_scroll: true,
 153        table_overflow_x_scroll: true,
 154        code_block: StyleRefinement {
 155            margin: EdgesRefinement {
 156                top: Some(Length::Definite(rems(0.).into())),
 157                left: Some(Length::Definite(rems(0.).into())),
 158                right: Some(Length::Definite(rems(0.).into())),
 159                bottom: Some(Length::Definite(rems(0.5).into())),
 160            },
 161            padding: EdgesRefinement {
 162                top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 163                left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 164                right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 165                bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 166            },
 167            background: Some(colors.editor_background.into()),
 168            border_color: Some(colors.border_variant),
 169            border_widths: EdgesRefinement {
 170                top: Some(AbsoluteLength::Pixels(Pixels(1.))),
 171                left: Some(AbsoluteLength::Pixels(Pixels(1.))),
 172                right: Some(AbsoluteLength::Pixels(Pixels(1.))),
 173                bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
 174            },
 175            text: Some(TextStyleRefinement {
 176                font_family: Some(theme_settings.buffer_font.family.clone()),
 177                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 178                font_features: Some(theme_settings.buffer_font.features.clone()),
 179                font_size: Some(buffer_font_size.into()),
 180                ..Default::default()
 181            }),
 182            ..Default::default()
 183        },
 184        inline_code: TextStyleRefinement {
 185            font_family: Some(theme_settings.buffer_font.family.clone()),
 186            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 187            font_features: Some(theme_settings.buffer_font.features.clone()),
 188            font_size: Some(buffer_font_size.into()),
 189            background_color: Some(colors.editor_foreground.opacity(0.1)),
 190            ..Default::default()
 191        },
 192        link: TextStyleRefinement {
 193            background_color: Some(colors.editor_foreground.opacity(0.025)),
 194            underline: Some(UnderlineStyle {
 195                color: Some(colors.text_accent.opacity(0.5)),
 196                thickness: px(1.),
 197                ..Default::default()
 198            }),
 199            ..Default::default()
 200        },
 201        ..Default::default()
 202    };
 203
 204    cx.new(|cx| Markdown::new(text, markdown_style, Some(language_registry), None, cx))
 205}
 206
 207struct EditMessageState {
 208    editor: Entity<Editor>,
 209}
 210
 211impl ActiveThread {
 212    pub fn new(
 213        thread: Entity<Thread>,
 214        thread_store: Entity<ThreadStore>,
 215        language_registry: Arc<LanguageRegistry>,
 216        context_store: Entity<ContextStore>,
 217        workspace: WeakEntity<Workspace>,
 218        window: &mut Window,
 219        cx: &mut Context<Self>,
 220    ) -> Self {
 221        let subscriptions = vec![
 222            cx.observe(&thread, |_, _, cx| cx.notify()),
 223            cx.subscribe_in(&thread, window, Self::handle_thread_event),
 224        ];
 225
 226        let mut this = Self {
 227            language_registry,
 228            thread_store,
 229            thread: thread.clone(),
 230            context_store,
 231            workspace,
 232            save_thread_task: None,
 233            messages: Vec::new(),
 234            rendered_messages_by_id: HashMap::default(),
 235            rendered_tool_use_labels: HashMap::default(),
 236            expanded_tool_uses: HashMap::default(),
 237            expanded_thinking_segments: HashMap::default(),
 238            list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
 239                let this = cx.entity().downgrade();
 240                move |ix, window: &mut Window, cx: &mut App| {
 241                    this.update(cx, |this, cx| this.render_message(ix, window, cx))
 242                        .unwrap()
 243                }
 244            }),
 245            editing_message: None,
 246            last_error: None,
 247            _subscriptions: subscriptions,
 248        };
 249
 250        for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
 251            this.push_message(&message.id, &message.segments, window, cx);
 252
 253            for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) {
 254                this.render_tool_use_label_markdown(
 255                    tool_use.id.clone(),
 256                    tool_use.ui_text.clone(),
 257                    window,
 258                    cx,
 259                );
 260            }
 261        }
 262
 263        this
 264    }
 265
 266    pub fn thread(&self) -> &Entity<Thread> {
 267        &self.thread
 268    }
 269
 270    pub fn is_empty(&self) -> bool {
 271        self.messages.is_empty()
 272    }
 273
 274    pub fn summary(&self, cx: &App) -> Option<SharedString> {
 275        self.thread.read(cx).summary()
 276    }
 277
 278    pub fn summary_or_default(&self, cx: &App) -> SharedString {
 279        self.thread.read(cx).summary_or_default()
 280    }
 281
 282    pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
 283        self.last_error.take();
 284        self.thread
 285            .update(cx, |thread, cx| thread.cancel_last_completion(cx))
 286    }
 287
 288    pub fn last_error(&self) -> Option<ThreadError> {
 289        self.last_error.clone()
 290    }
 291
 292    pub fn clear_last_error(&mut self) {
 293        self.last_error.take();
 294    }
 295
 296    fn push_message(
 297        &mut self,
 298        id: &MessageId,
 299        segments: &[MessageSegment],
 300        window: &mut Window,
 301        cx: &mut Context<Self>,
 302    ) {
 303        let old_len = self.messages.len();
 304        self.messages.push(*id);
 305        self.list_state.splice(old_len..old_len, 1);
 306
 307        let rendered_message =
 308            RenderedMessage::from_segments(segments, self.language_registry.clone(), window, cx);
 309        self.rendered_messages_by_id.insert(*id, rendered_message);
 310        self.list_state.scroll_to(ListOffset {
 311            item_ix: old_len,
 312            offset_in_item: Pixels(0.0),
 313        });
 314    }
 315
 316    fn edited_message(
 317        &mut self,
 318        id: &MessageId,
 319        segments: &[MessageSegment],
 320        window: &mut Window,
 321        cx: &mut Context<Self>,
 322    ) {
 323        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
 324            return;
 325        };
 326        self.list_state.splice(index..index + 1, 1);
 327        let rendered_message =
 328            RenderedMessage::from_segments(segments, self.language_registry.clone(), window, cx);
 329        self.rendered_messages_by_id.insert(*id, rendered_message);
 330    }
 331
 332    fn deleted_message(&mut self, id: &MessageId) {
 333        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
 334            return;
 335        };
 336        self.messages.remove(index);
 337        self.list_state.splice(index..index + 1, 0);
 338        self.rendered_messages_by_id.remove(id);
 339    }
 340
 341    fn render_tool_use_label_markdown(
 342        &mut self,
 343        tool_use_id: LanguageModelToolUseId,
 344        tool_label: impl Into<SharedString>,
 345        window: &mut Window,
 346        cx: &mut Context<Self>,
 347    ) {
 348        self.rendered_tool_use_labels.insert(
 349            tool_use_id,
 350            render_markdown(
 351                tool_label.into(),
 352                self.language_registry.clone(),
 353                window,
 354                cx,
 355            ),
 356        );
 357    }
 358
 359    fn handle_thread_event(
 360        &mut self,
 361        _thread: &Entity<Thread>,
 362        event: &ThreadEvent,
 363        window: &mut Window,
 364        cx: &mut Context<Self>,
 365    ) {
 366        match event {
 367            ThreadEvent::ShowError(error) => {
 368                self.last_error = Some(error.clone());
 369            }
 370            ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => {
 371                self.save_thread(cx);
 372            }
 373            ThreadEvent::DoneStreaming => {}
 374            ThreadEvent::StreamedAssistantText(message_id, text) => {
 375                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
 376                    rendered_message.append_text(text, window, cx);
 377                }
 378            }
 379            ThreadEvent::StreamedAssistantThinking(message_id, text) => {
 380                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
 381                    rendered_message.append_thinking(text, window, cx);
 382                }
 383            }
 384            ThreadEvent::MessageAdded(message_id) => {
 385                if let Some(message_segments) = self
 386                    .thread
 387                    .read(cx)
 388                    .message(*message_id)
 389                    .map(|message| message.segments.clone())
 390                {
 391                    self.push_message(message_id, &message_segments, window, cx);
 392                }
 393
 394                self.save_thread(cx);
 395                cx.notify();
 396            }
 397            ThreadEvent::MessageEdited(message_id) => {
 398                if let Some(message_segments) = self
 399                    .thread
 400                    .read(cx)
 401                    .message(*message_id)
 402                    .map(|message| message.segments.clone())
 403                {
 404                    self.edited_message(message_id, &message_segments, window, cx);
 405                }
 406
 407                self.save_thread(cx);
 408                cx.notify();
 409            }
 410            ThreadEvent::MessageDeleted(message_id) => {
 411                self.deleted_message(message_id);
 412                self.save_thread(cx);
 413                cx.notify();
 414            }
 415            ThreadEvent::UsePendingTools => {
 416                let tool_uses = self
 417                    .thread
 418                    .update(cx, |thread, cx| thread.use_pending_tools(cx));
 419
 420                for tool_use in tool_uses {
 421                    self.render_tool_use_label_markdown(
 422                        tool_use.id.clone(),
 423                        tool_use.ui_text.clone(),
 424                        window,
 425                        cx,
 426                    );
 427                }
 428            }
 429            ThreadEvent::ToolFinished {
 430                pending_tool_use,
 431                canceled,
 432                ..
 433            } => {
 434                let canceled = *canceled;
 435                if let Some(tool_use) = pending_tool_use {
 436                    self.render_tool_use_label_markdown(
 437                        tool_use.id.clone(),
 438                        SharedString::from(tool_use.ui_text.clone()),
 439                        window,
 440                        cx,
 441                    );
 442                }
 443
 444                if self.thread.read(cx).all_tools_finished() {
 445                    let pending_refresh_buffers = self.thread.update(cx, |thread, cx| {
 446                        thread.action_log().update(cx, |action_log, _cx| {
 447                            action_log.take_stale_buffers_in_context()
 448                        })
 449                    });
 450
 451                    let context_update_task = if !pending_refresh_buffers.is_empty() {
 452                        let refresh_task = refresh_context_store_text(
 453                            self.context_store.clone(),
 454                            &pending_refresh_buffers,
 455                            cx,
 456                        );
 457
 458                        cx.spawn(async move |this, cx| {
 459                            let updated_context_ids = refresh_task.await;
 460
 461                            this.update(cx, |this, cx| {
 462                                this.context_store.read_with(cx, |context_store, cx| {
 463                                    context_store
 464                                        .context()
 465                                        .iter()
 466                                        .filter(|context| {
 467                                            updated_context_ids.contains(&context.id())
 468                                        })
 469                                        .flat_map(|context| context.snapshot(cx))
 470                                        .collect()
 471                                })
 472                            })
 473                        })
 474                    } else {
 475                        Task::ready(anyhow::Ok(Vec::new()))
 476                    };
 477
 478                    let model_registry = LanguageModelRegistry::read_global(cx);
 479                    if let Some(model) = model_registry.active_model() {
 480                        cx.spawn(async move |this, cx| {
 481                            let updated_context = context_update_task.await?;
 482
 483                            this.update(cx, |this, cx| {
 484                                this.thread.update(cx, |thread, cx| {
 485                                    thread.attach_tool_results(updated_context, cx);
 486                                    if !canceled {
 487                                        thread.send_to_model(model, RequestKind::Chat, cx);
 488                                    }
 489                                });
 490                            })
 491                        })
 492                        .detach();
 493                    }
 494                }
 495            }
 496            ThreadEvent::CheckpointChanged => cx.notify(),
 497        }
 498    }
 499
 500    /// Spawns a task to save the active thread.
 501    ///
 502    /// Only one task to save the thread will be in flight at a time.
 503    fn save_thread(&mut self, cx: &mut Context<Self>) {
 504        let thread = self.thread.clone();
 505        self.save_thread_task = Some(cx.spawn(async move |this, cx| {
 506            let task = this
 507                .update(cx, |this, cx| {
 508                    this.thread_store
 509                        .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx))
 510                })
 511                .ok();
 512
 513            if let Some(task) = task {
 514                task.await.log_err();
 515            }
 516        }));
 517    }
 518
 519    fn start_editing_message(
 520        &mut self,
 521        message_id: MessageId,
 522        message_segments: &[MessageSegment],
 523        window: &mut Window,
 524        cx: &mut Context<Self>,
 525    ) {
 526        // User message should always consist of a single text segment,
 527        // therefore we can skip returning early if it's not a text segment.
 528        let Some(MessageSegment::Text(message_text)) = message_segments.first() else {
 529            return;
 530        };
 531
 532        let buffer = cx.new(|cx| {
 533            MultiBuffer::singleton(cx.new(|cx| Buffer::local(message_text.clone(), cx)), cx)
 534        });
 535        let editor = cx.new(|cx| {
 536            let mut editor = Editor::new(
 537                editor::EditorMode::AutoHeight { max_lines: 8 },
 538                buffer,
 539                None,
 540                window,
 541                cx,
 542            );
 543            editor.focus_handle(cx).focus(window);
 544            editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
 545            editor
 546        });
 547        self.editing_message = Some((
 548            message_id,
 549            EditMessageState {
 550                editor: editor.clone(),
 551            },
 552        ));
 553        cx.notify();
 554    }
 555
 556    fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
 557        self.editing_message.take();
 558        cx.notify();
 559    }
 560
 561    fn confirm_editing_message(
 562        &mut self,
 563        _: &menu::Confirm,
 564        _: &mut Window,
 565        cx: &mut Context<Self>,
 566    ) {
 567        let Some((message_id, state)) = self.editing_message.take() else {
 568            return;
 569        };
 570        let edited_text = state.editor.read(cx).text(cx);
 571        self.thread.update(cx, |thread, cx| {
 572            thread.edit_message(
 573                message_id,
 574                Role::User,
 575                vec![MessageSegment::Text(edited_text)],
 576                cx,
 577            );
 578            for message_id in self.messages_after(message_id) {
 579                thread.delete_message(*message_id, cx);
 580            }
 581        });
 582
 583        let provider = LanguageModelRegistry::read_global(cx).active_provider();
 584        if provider
 585            .as_ref()
 586            .map_or(false, |provider| provider.must_accept_terms(cx))
 587        {
 588            cx.notify();
 589            return;
 590        }
 591        let model_registry = LanguageModelRegistry::read_global(cx);
 592        let Some(model) = model_registry.active_model() else {
 593            return;
 594        };
 595
 596        self.thread.update(cx, |thread, cx| {
 597            thread.send_to_model(model, RequestKind::Chat, cx)
 598        });
 599        cx.notify();
 600    }
 601
 602    fn last_user_message(&self, cx: &Context<Self>) -> Option<MessageId> {
 603        self.messages
 604            .iter()
 605            .rev()
 606            .find(|message_id| {
 607                self.thread
 608                    .read(cx)
 609                    .message(**message_id)
 610                    .map_or(false, |message| message.role == Role::User)
 611            })
 612            .cloned()
 613    }
 614
 615    fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
 616        self.messages
 617            .iter()
 618            .position(|id| *id == message_id)
 619            .map(|index| &self.messages[index + 1..])
 620            .unwrap_or(&[])
 621    }
 622
 623    fn handle_cancel_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
 624        self.cancel_editing_message(&menu::Cancel, window, cx);
 625    }
 626
 627    fn handle_regenerate_click(
 628        &mut self,
 629        _: &ClickEvent,
 630        window: &mut Window,
 631        cx: &mut Context<Self>,
 632    ) {
 633        self.confirm_editing_message(&menu::Confirm, window, cx);
 634    }
 635
 636    fn handle_feedback_click(
 637        &mut self,
 638        feedback: ThreadFeedback,
 639        _window: &mut Window,
 640        cx: &mut Context<Self>,
 641    ) {
 642        let report = self
 643            .thread
 644            .update(cx, |thread, cx| thread.report_feedback(feedback, cx));
 645
 646        let this = cx.entity().downgrade();
 647        cx.spawn(async move |_, cx| {
 648            report.await?;
 649            this.update(cx, |_this, cx| cx.notify())
 650        })
 651        .detach_and_log_err(cx);
 652    }
 653
 654    fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
 655        let message_id = self.messages[ix];
 656        let Some(message) = self.thread.read(cx).message(message_id) else {
 657            return Empty.into_any();
 658        };
 659
 660        let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
 661            return Empty.into_any();
 662        };
 663
 664        let thread = self.thread.read(cx);
 665        // Get all the data we need from thread before we start using it in closures
 666        let checkpoint = thread.checkpoint_for_message(message_id);
 667        let context = thread.context_for_message(message_id);
 668        let tool_uses = thread.tool_uses_for_message(message_id, cx);
 669
 670        // Don't render user messages that are just there for returning tool results.
 671        if message.role == Role::User && thread.message_has_tool_results(message_id) {
 672            return Empty.into_any();
 673        }
 674
 675        let allow_editing_message =
 676            message.role == Role::User && self.last_user_message(cx) == Some(message_id);
 677
 678        let edit_message_editor = self
 679            .editing_message
 680            .as_ref()
 681            .filter(|(id, _)| *id == message_id)
 682            .map(|(_, state)| state.editor.clone());
 683
 684        let first_message = ix == 0;
 685        let is_last_message = ix == self.messages.len() - 1;
 686
 687        let colors = cx.theme().colors();
 688        let active_color = colors.element_active;
 689        let editor_bg_color = colors.editor_background;
 690        let bg_user_message_header = editor_bg_color.blend(active_color.opacity(0.25));
 691
 692        let feedback_container = h_flex().pt_2().pb_4().px_4().gap_1().justify_between();
 693        let feedback_items = match self.thread.read(cx).feedback() {
 694            Some(feedback) => feedback_container
 695                .child(
 696                    Label::new(match feedback {
 697                        ThreadFeedback::Positive => "Thanks for your feedback!",
 698                        ThreadFeedback::Negative => {
 699                            "We appreciate your feedback and will use it to improve."
 700                        }
 701                    })
 702                    .color(Color::Muted)
 703                    .size(LabelSize::XSmall),
 704                )
 705                .child(
 706                    h_flex()
 707                        .gap_1()
 708                        .child(
 709                            IconButton::new("feedback-thumbs-up", IconName::ThumbsUp)
 710                                .icon_size(IconSize::XSmall)
 711                                .icon_color(match feedback {
 712                                    ThreadFeedback::Positive => Color::Accent,
 713                                    ThreadFeedback::Negative => Color::Ignored,
 714                                })
 715                                .shape(ui::IconButtonShape::Square)
 716                                .tooltip(Tooltip::text("Helpful Response"))
 717                                .on_click(cx.listener(move |this, _, window, cx| {
 718                                    this.handle_feedback_click(
 719                                        ThreadFeedback::Positive,
 720                                        window,
 721                                        cx,
 722                                    );
 723                                })),
 724                        )
 725                        .child(
 726                            IconButton::new("feedback-thumbs-down", IconName::ThumbsDown)
 727                                .icon_size(IconSize::XSmall)
 728                                .icon_color(match feedback {
 729                                    ThreadFeedback::Positive => Color::Ignored,
 730                                    ThreadFeedback::Negative => Color::Accent,
 731                                })
 732                                .shape(ui::IconButtonShape::Square)
 733                                .tooltip(Tooltip::text("Not Helpful"))
 734                                .on_click(cx.listener(move |this, _, window, cx| {
 735                                    this.handle_feedback_click(
 736                                        ThreadFeedback::Negative,
 737                                        window,
 738                                        cx,
 739                                    );
 740                                })),
 741                        ),
 742                )
 743                .into_any_element(),
 744            None => feedback_container
 745                .child(
 746                    Label::new(
 747                        "Rating the thread sends all of your current conversation to the Zed team.",
 748                    )
 749                    .color(Color::Muted)
 750                    .size(LabelSize::XSmall),
 751                )
 752                .child(
 753                    h_flex()
 754                        .gap_1()
 755                        .child(
 756                            IconButton::new("feedback-thumbs-up", IconName::ThumbsUp)
 757                                .icon_size(IconSize::XSmall)
 758                                .icon_color(Color::Ignored)
 759                                .shape(ui::IconButtonShape::Square)
 760                                .tooltip(Tooltip::text("Helpful Response"))
 761                                .on_click(cx.listener(move |this, _, window, cx| {
 762                                    this.handle_feedback_click(
 763                                        ThreadFeedback::Positive,
 764                                        window,
 765                                        cx,
 766                                    );
 767                                })),
 768                        )
 769                        .child(
 770                            IconButton::new("feedback-thumbs-down", IconName::ThumbsDown)
 771                                .icon_size(IconSize::XSmall)
 772                                .icon_color(Color::Ignored)
 773                                .shape(ui::IconButtonShape::Square)
 774                                .tooltip(Tooltip::text("Not Helpful"))
 775                                .on_click(cx.listener(move |this, _, window, cx| {
 776                                    this.handle_feedback_click(
 777                                        ThreadFeedback::Negative,
 778                                        window,
 779                                        cx,
 780                                    );
 781                                })),
 782                        ),
 783                )
 784                .into_any_element(),
 785        };
 786
 787        let message_content = v_flex()
 788            .gap_1p5()
 789            .child(
 790                if let Some(edit_message_editor) = edit_message_editor.clone() {
 791                    div()
 792                        .key_context("EditMessageEditor")
 793                        .on_action(cx.listener(Self::cancel_editing_message))
 794                        .on_action(cx.listener(Self::confirm_editing_message))
 795                        .min_h_6()
 796                        .child(edit_message_editor)
 797                } else {
 798                    div()
 799                        .min_h_6()
 800                        .text_ui(cx)
 801                        .child(self.render_message_content(message_id, rendered_message, cx))
 802                },
 803            )
 804            .when_some(context, |parent, context| {
 805                if !context.is_empty() {
 806                    parent.child(
 807                        h_flex().flex_wrap().gap_1().children(
 808                            context
 809                                .into_iter()
 810                                .map(|context| ContextPill::added(context, false, false, None)),
 811                        ),
 812                    )
 813                } else {
 814                    parent
 815                }
 816            });
 817
 818        let styled_message = match message.role {
 819            Role::User => v_flex()
 820                .id(("message-container", ix))
 821                .map(|this| {
 822                    if first_message {
 823                        this.pt_2()
 824                    } else {
 825                        this.pt_4()
 826                    }
 827                })
 828                .pb_4()
 829                .pl_2()
 830                .pr_2p5()
 831                .child(
 832                    v_flex()
 833                        .bg(colors.editor_background)
 834                        .rounded_lg()
 835                        .border_1()
 836                        .border_color(colors.border)
 837                        .shadow_md()
 838                        .child(
 839                            h_flex()
 840                                .py_1()
 841                                .pl_2()
 842                                .pr_1()
 843                                .bg(bg_user_message_header)
 844                                .border_b_1()
 845                                .border_color(colors.border)
 846                                .justify_between()
 847                                .rounded_t_md()
 848                                .child(
 849                                    h_flex()
 850                                        .gap_1p5()
 851                                        .child(
 852                                            Icon::new(IconName::PersonCircle)
 853                                                .size(IconSize::XSmall)
 854                                                .color(Color::Muted),
 855                                        )
 856                                        .child(
 857                                            Label::new("You")
 858                                                .size(LabelSize::Small)
 859                                                .color(Color::Muted),
 860                                        ),
 861                                )
 862                                .child(
 863                                    h_flex()
 864                                        // DL: To double-check whether we want to fully remove
 865                                        // the editing feature from meassages. Checkpoint sort of
 866                                        // solve the same problem.
 867                                        .invisible()
 868                                        .gap_1()
 869                                        .when_some(
 870                                            edit_message_editor.clone(),
 871                                            |this, edit_message_editor| {
 872                                                let focus_handle =
 873                                                    edit_message_editor.focus_handle(cx);
 874                                                this.child(
 875                                                    Button::new("cancel-edit-message", "Cancel")
 876                                                        .label_size(LabelSize::Small)
 877                                                        .key_binding(
 878                                                            KeyBinding::for_action_in(
 879                                                                &menu::Cancel,
 880                                                                &focus_handle,
 881                                                                window,
 882                                                                cx,
 883                                                            )
 884                                                            .map(|kb| kb.size(rems_from_px(12.))),
 885                                                        )
 886                                                        .on_click(
 887                                                            cx.listener(Self::handle_cancel_click),
 888                                                        ),
 889                                                )
 890                                                .child(
 891                                                    Button::new(
 892                                                        "confirm-edit-message",
 893                                                        "Regenerate",
 894                                                    )
 895                                                    .label_size(LabelSize::Small)
 896                                                    .key_binding(
 897                                                        KeyBinding::for_action_in(
 898                                                            &menu::Confirm,
 899                                                            &focus_handle,
 900                                                            window,
 901                                                            cx,
 902                                                        )
 903                                                        .map(|kb| kb.size(rems_from_px(12.))),
 904                                                    )
 905                                                    .on_click(
 906                                                        cx.listener(Self::handle_regenerate_click),
 907                                                    ),
 908                                                )
 909                                            },
 910                                        )
 911                                        .when(
 912                                            edit_message_editor.is_none() && allow_editing_message,
 913                                            |this| {
 914                                                this.child(
 915                                                    Button::new("edit-message", "Edit")
 916                                                        .label_size(LabelSize::Small)
 917                                                        .on_click(cx.listener({
 918                                                            let message_segments =
 919                                                                message.segments.clone();
 920                                                            move |this, _, window, cx| {
 921                                                                this.start_editing_message(
 922                                                                    message_id,
 923                                                                    &message_segments,
 924                                                                    window,
 925                                                                    cx,
 926                                                                );
 927                                                            }
 928                                                        })),
 929                                                )
 930                                            },
 931                                        ),
 932                                ),
 933                        )
 934                        .child(div().p_2().child(message_content)),
 935                ),
 936            Role::Assistant => v_flex()
 937                .id(("message-container", ix))
 938                .ml_2()
 939                .pl_2()
 940                .pr_4()
 941                .border_l_1()
 942                .border_color(cx.theme().colors().border_variant)
 943                .child(message_content)
 944                .when(!tool_uses.is_empty(), |parent| {
 945                    parent.child(
 946                        v_flex().children(
 947                            tool_uses
 948                                .into_iter()
 949                                .map(|tool_use| self.render_tool_use(tool_use, cx)),
 950                        ),
 951                    )
 952                }),
 953            Role::System => div().id(("message-container", ix)).py_1().px_2().child(
 954                v_flex()
 955                    .bg(colors.editor_background)
 956                    .rounded_sm()
 957                    .child(div().p_4().child(message_content)),
 958            ),
 959        };
 960
 961        v_flex()
 962            .w_full()
 963            .when(first_message, |parent| {
 964                parent.child(self.render_rules_item(cx))
 965            })
 966            .when_some(checkpoint, |parent, checkpoint| {
 967                let mut is_pending = false;
 968                let mut error = None;
 969                if let Some(last_restore_checkpoint) =
 970                    self.thread.read(cx).last_restore_checkpoint()
 971                {
 972                    if last_restore_checkpoint.message_id() == message_id {
 973                        match last_restore_checkpoint {
 974                            LastRestoreCheckpoint::Pending { .. } => is_pending = true,
 975                            LastRestoreCheckpoint::Error { error: err, .. } => {
 976                                error = Some(err.clone());
 977                            }
 978                        }
 979                    }
 980                }
 981
 982                let restore_checkpoint_button =
 983                    Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
 984                        .icon(if error.is_some() {
 985                            IconName::XCircle
 986                        } else {
 987                            IconName::Undo
 988                        })
 989                        .icon_size(IconSize::XSmall)
 990                        .icon_position(IconPosition::Start)
 991                        .icon_color(if error.is_some() {
 992                            Some(Color::Error)
 993                        } else {
 994                            None
 995                        })
 996                        .label_size(LabelSize::XSmall)
 997                        .disabled(is_pending)
 998                        .on_click(cx.listener(move |this, _, _window, cx| {
 999                            this.thread.update(cx, |thread, cx| {
1000                                thread
1001                                    .restore_checkpoint(checkpoint.clone(), cx)
1002                                    .detach_and_log_err(cx);
1003                            });
1004                        }));
1005
1006                let restore_checkpoint_button = if is_pending {
1007                    restore_checkpoint_button
1008                        .with_animation(
1009                            ("pulsating-restore-checkpoint-button", ix),
1010                            Animation::new(Duration::from_secs(2))
1011                                .repeat()
1012                                .with_easing(pulsating_between(0.6, 1.)),
1013                            |label, delta| label.alpha(delta),
1014                        )
1015                        .into_any_element()
1016                } else if let Some(error) = error {
1017                    restore_checkpoint_button
1018                        .tooltip(Tooltip::text(error.to_string()))
1019                        .into_any_element()
1020                } else {
1021                    restore_checkpoint_button.into_any_element()
1022                };
1023
1024                parent.child(
1025                    h_flex()
1026                        .pt_2p5()
1027                        .px_2p5()
1028                        .w_full()
1029                        .gap_1()
1030                        .child(ui::Divider::horizontal())
1031                        .child(restore_checkpoint_button)
1032                        .child(ui::Divider::horizontal()),
1033                )
1034            })
1035            .child(styled_message)
1036            .when(
1037                is_last_message && !self.thread.read(cx).is_generating(),
1038                |parent| parent.child(feedback_items),
1039            )
1040            .into_any()
1041    }
1042
1043    fn render_message_content(
1044        &self,
1045        message_id: MessageId,
1046        rendered_message: &RenderedMessage,
1047        cx: &Context<Self>,
1048    ) -> impl IntoElement {
1049        let pending_thinking_segment_index = rendered_message
1050            .segments
1051            .iter()
1052            .enumerate()
1053            .last()
1054            .filter(|(_, segment)| matches!(segment, RenderedMessageSegment::Thinking { .. }))
1055            .map(|(index, _)| index);
1056
1057        div()
1058            .text_ui(cx)
1059            .gap_2()
1060            .children(
1061                rendered_message.segments.iter().enumerate().map(
1062                    |(index, segment)| match segment {
1063                        RenderedMessageSegment::Thinking {
1064                            content,
1065                            scroll_handle,
1066                        } => self
1067                            .render_message_thinking_segment(
1068                                message_id,
1069                                index,
1070                                content.clone(),
1071                                &scroll_handle,
1072                                Some(index) == pending_thinking_segment_index,
1073                                cx,
1074                            )
1075                            .into_any_element(),
1076                        RenderedMessageSegment::Text(markdown) => {
1077                            div().child(markdown.clone()).into_any_element()
1078                        }
1079                    },
1080                ),
1081            )
1082    }
1083
1084    fn render_message_thinking_segment(
1085        &self,
1086        message_id: MessageId,
1087        ix: usize,
1088        markdown: Entity<Markdown>,
1089        scroll_handle: &ScrollHandle,
1090        pending: bool,
1091        cx: &Context<Self>,
1092    ) -> impl IntoElement {
1093        let is_open = self
1094            .expanded_thinking_segments
1095            .get(&(message_id, ix))
1096            .copied()
1097            .unwrap_or_default();
1098
1099        let lighter_border = cx.theme().colors().border.opacity(0.5);
1100        let editor_bg = cx.theme().colors().editor_background;
1101
1102        div().py_2().child(
1103            v_flex()
1104                .rounded_lg()
1105                .border_1()
1106                .border_color(lighter_border)
1107                .child(
1108                    h_flex()
1109                        .group("disclosure-header")
1110                        .justify_between()
1111                        .py_1()
1112                        .px_2()
1113                        .bg(cx.theme().colors().editor_foreground.opacity(0.025))
1114                        .map(|this| {
1115                            if pending || is_open {
1116                                this.rounded_t_md()
1117                                    .border_b_1()
1118                                    .border_color(lighter_border)
1119                            } else {
1120                                this.rounded_md()
1121                            }
1122                        })
1123                        .child(
1124                            h_flex()
1125                                .gap_1p5()
1126                                .child(
1127                                    Icon::new(IconName::Brain)
1128                                        .size(IconSize::XSmall)
1129                                        .color(Color::Muted),
1130                                )
1131                                .child({
1132                                    if pending {
1133                                        Label::new("Thinking…")
1134                                            .size(LabelSize::Small)
1135                                            .buffer_font(cx)
1136                                            .with_animation(
1137                                                "pulsating-label",
1138                                                Animation::new(Duration::from_secs(2))
1139                                                    .repeat()
1140                                                    .with_easing(pulsating_between(0.4, 0.8)),
1141                                                |label, delta| label.alpha(delta),
1142                                            )
1143                                            .into_any_element()
1144                                    } else {
1145                                        Label::new("Thought Process")
1146                                            .size(LabelSize::Small)
1147                                            .buffer_font(cx)
1148                                            .into_any_element()
1149                                    }
1150                                }),
1151                        )
1152                        .child(
1153                            h_flex()
1154                                .gap_1()
1155                                .child(
1156                                    div().visible_on_hover("disclosure-header").child(
1157                                        Disclosure::new("thinking-disclosure", is_open)
1158                                            .opened_icon(IconName::ChevronUp)
1159                                            .closed_icon(IconName::ChevronDown)
1160                                            .on_click(cx.listener({
1161                                                move |this, _event, _window, _cx| {
1162                                                    let is_open = this
1163                                                        .expanded_thinking_segments
1164                                                        .entry((message_id, ix))
1165                                                        .or_insert(false);
1166
1167                                                    *is_open = !*is_open;
1168                                                }
1169                                            })),
1170                                    ),
1171                                )
1172                                .child({
1173                                    let (icon_name, color, animated) = if pending {
1174                                        (IconName::ArrowCircle, Color::Accent, true)
1175                                    } else {
1176                                        (IconName::Check, Color::Success, false)
1177                                    };
1178
1179                                    let icon =
1180                                        Icon::new(icon_name).color(color).size(IconSize::Small);
1181
1182                                    if animated {
1183                                        icon.with_animation(
1184                                            "arrow-circle",
1185                                            Animation::new(Duration::from_secs(2)).repeat(),
1186                                            |icon, delta| {
1187                                                icon.transform(Transformation::rotate(percentage(
1188                                                    delta,
1189                                                )))
1190                                            },
1191                                        )
1192                                        .into_any_element()
1193                                    } else {
1194                                        icon.into_any_element()
1195                                    }
1196                                }),
1197                        ),
1198                )
1199                .when(pending && !is_open, |this| {
1200                    let gradient_overlay = div()
1201                        .rounded_b_lg()
1202                        .h_20()
1203                        .absolute()
1204                        .w_full()
1205                        .bottom_0()
1206                        .left_0()
1207                        .bg(linear_gradient(
1208                            180.,
1209                            linear_color_stop(editor_bg, 1.),
1210                            linear_color_stop(editor_bg.opacity(0.2), 0.),
1211                        ));
1212
1213                    this.child(
1214                        div()
1215                            .relative()
1216                            .bg(editor_bg)
1217                            .rounded_b_lg()
1218                            .child(
1219                                div()
1220                                    .id(("thinking-content", ix))
1221                                    .p_2()
1222                                    .h_20()
1223                                    .track_scroll(scroll_handle)
1224                                    .text_ui_sm(cx)
1225                                    .child(markdown.clone())
1226                                    .overflow_hidden(),
1227                            )
1228                            .child(gradient_overlay),
1229                    )
1230                })
1231                .when(is_open, |this| {
1232                    this.child(
1233                        div()
1234                            .id(("thinking-content", ix))
1235                            .h_full()
1236                            .p_2()
1237                            .rounded_b_lg()
1238                            .bg(editor_bg)
1239                            .text_ui_sm(cx)
1240                            .child(markdown.clone()),
1241                    )
1242                }),
1243        )
1244    }
1245
1246    fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement {
1247        let is_open = self
1248            .expanded_tool_uses
1249            .get(&tool_use.id)
1250            .copied()
1251            .unwrap_or_default();
1252
1253        let lighter_border = cx.theme().colors().border.opacity(0.5);
1254
1255        div().py_2().child(
1256            v_flex()
1257                .rounded_lg()
1258                .border_1()
1259                .border_color(lighter_border)
1260                .overflow_hidden()
1261                .child(
1262                    h_flex()
1263                        .group("disclosure-header")
1264                        .justify_between()
1265                        .py_1()
1266                        .px_2()
1267                        .bg(cx.theme().colors().editor_foreground.opacity(0.025))
1268                        .map(|element| {
1269                            if is_open {
1270                                element.border_b_1().rounded_t_md()
1271                            } else {
1272                                element.rounded_md()
1273                            }
1274                        })
1275                        .border_color(lighter_border)
1276                        .child(
1277                            h_flex()
1278                                .gap_1p5()
1279                                .child(
1280                                    Icon::new(tool_use.icon)
1281                                        .size(IconSize::XSmall)
1282                                        .color(Color::Muted),
1283                                )
1284                                .child(
1285                                    div()
1286                                        .text_ui_sm(cx)
1287                                        .children(
1288                                            self.rendered_tool_use_labels
1289                                                .get(&tool_use.id)
1290                                                .cloned(),
1291                                        )
1292                                        .truncate(),
1293                                ),
1294                        )
1295                        .child(
1296                            h_flex()
1297                                .gap_1()
1298                                .child(
1299                                    div().visible_on_hover("disclosure-header").child(
1300                                        Disclosure::new("tool-use-disclosure", is_open)
1301                                            .opened_icon(IconName::ChevronUp)
1302                                            .closed_icon(IconName::ChevronDown)
1303                                            .on_click(cx.listener({
1304                                                let tool_use_id = tool_use.id.clone();
1305                                                move |this, _event, _window, _cx| {
1306                                                    let is_open = this
1307                                                        .expanded_tool_uses
1308                                                        .entry(tool_use_id.clone())
1309                                                        .or_insert(false);
1310
1311                                                    *is_open = !*is_open;
1312                                                }
1313                                            })),
1314                                    ),
1315                                )
1316                                .child({
1317                                    let (icon_name, color, animated) = match &tool_use.status {
1318                                        ToolUseStatus::Pending
1319                                        | ToolUseStatus::NeedsConfirmation => {
1320                                            (IconName::Warning, Color::Warning, false)
1321                                        }
1322                                        ToolUseStatus::Running => {
1323                                            (IconName::ArrowCircle, Color::Accent, true)
1324                                        }
1325                                        ToolUseStatus::Finished(_) => {
1326                                            (IconName::Check, Color::Success, false)
1327                                        }
1328                                        ToolUseStatus::Error(_) => {
1329                                            (IconName::Close, Color::Error, false)
1330                                        }
1331                                    };
1332
1333                                    let icon =
1334                                        Icon::new(icon_name).color(color).size(IconSize::Small);
1335
1336                                    if animated {
1337                                        icon.with_animation(
1338                                            "arrow-circle",
1339                                            Animation::new(Duration::from_secs(2)).repeat(),
1340                                            |icon, delta| {
1341                                                icon.transform(Transformation::rotate(percentage(
1342                                                    delta,
1343                                                )))
1344                                            },
1345                                        )
1346                                        .into_any_element()
1347                                    } else {
1348                                        icon.into_any_element()
1349                                    }
1350                                }),
1351                        ),
1352                )
1353                .map(|parent| {
1354                    if !is_open {
1355                        return parent;
1356                    }
1357
1358                    let content_container = || v_flex().py_1().gap_0p5().px_2p5();
1359
1360                    parent.child(
1361                        v_flex()
1362                            .gap_1()
1363                            .bg(cx.theme().colors().editor_background)
1364                            .rounded_b_lg()
1365                            .child(
1366                                content_container()
1367                                    .border_b_1()
1368                                    .border_color(lighter_border)
1369                                    .child(
1370                                        Label::new("Input")
1371                                            .size(LabelSize::XSmall)
1372                                            .color(Color::Muted)
1373                                            .buffer_font(cx),
1374                                    )
1375                                    .child(
1376                                        Label::new(
1377                                            serde_json::to_string_pretty(&tool_use.input)
1378                                                .unwrap_or_default(),
1379                                        )
1380                                        .size(LabelSize::Small)
1381                                        .buffer_font(cx),
1382                                    ),
1383                            )
1384                            .map(|container| match tool_use.status {
1385                                ToolUseStatus::Finished(output) => container.child(
1386                                    content_container()
1387                                        .child(
1388                                            Label::new("Result")
1389                                                .size(LabelSize::XSmall)
1390                                                .color(Color::Muted)
1391                                                .buffer_font(cx),
1392                                        )
1393                                        .child(
1394                                            Label::new(output)
1395                                                .size(LabelSize::Small)
1396                                                .buffer_font(cx),
1397                                        ),
1398                                ),
1399                                ToolUseStatus::Running => container.child(
1400                                    content_container().child(
1401                                        h_flex()
1402                                            .gap_1()
1403                                            .pb_1()
1404                                            .child(
1405                                                Icon::new(IconName::ArrowCircle)
1406                                                    .size(IconSize::Small)
1407                                                    .color(Color::Accent)
1408                                                    .with_animation(
1409                                                        "arrow-circle",
1410                                                        Animation::new(Duration::from_secs(2))
1411                                                            .repeat(),
1412                                                        |icon, delta| {
1413                                                            icon.transform(Transformation::rotate(
1414                                                                percentage(delta),
1415                                                            ))
1416                                                        },
1417                                                    ),
1418                                            )
1419                                            .child(
1420                                                Label::new("Running…")
1421                                                    .size(LabelSize::XSmall)
1422                                                    .color(Color::Muted)
1423                                                    .buffer_font(cx),
1424                                            ),
1425                                    ),
1426                                ),
1427                                ToolUseStatus::Error(err) => container.child(
1428                                    content_container()
1429                                        .child(
1430                                            Label::new("Error")
1431                                                .size(LabelSize::XSmall)
1432                                                .color(Color::Muted)
1433                                                .buffer_font(cx),
1434                                        )
1435                                        .child(
1436                                            Label::new(err).size(LabelSize::Small).buffer_font(cx),
1437                                        ),
1438                                ),
1439                                ToolUseStatus::Pending => container,
1440                                ToolUseStatus::NeedsConfirmation => container.child(
1441                                    content_container().child(
1442                                        Label::new("Asking Permission")
1443                                            .size(LabelSize::Small)
1444                                            .color(Color::Muted)
1445                                            .buffer_font(cx),
1446                                    ),
1447                                ),
1448                            }),
1449                    )
1450                }),
1451        )
1452    }
1453
1454    fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
1455        let Some(system_prompt_context) = self.thread.read(cx).system_prompt_context().as_ref()
1456        else {
1457            return div().into_any();
1458        };
1459
1460        let rules_files = system_prompt_context
1461            .worktrees
1462            .iter()
1463            .filter_map(|worktree| worktree.rules_file.as_ref())
1464            .collect::<Vec<_>>();
1465
1466        let label_text = match rules_files.as_slice() {
1467            &[] => return div().into_any(),
1468            &[rules_file] => {
1469                format!("Using {:?} file", rules_file.rel_path)
1470            }
1471            rules_files => {
1472                format!("Using {} rules files", rules_files.len())
1473            }
1474        };
1475
1476        div()
1477            .pt_1()
1478            .px_2p5()
1479            .child(
1480                h_flex()
1481                    .w_full()
1482                    .gap_0p5()
1483                    .child(
1484                        h_flex()
1485                            .gap_1p5()
1486                            .child(
1487                                Icon::new(IconName::File)
1488                                    .size(IconSize::XSmall)
1489                                    .color(Color::Disabled),
1490                            )
1491                            .child(
1492                                Label::new(label_text)
1493                                    .size(LabelSize::XSmall)
1494                                    .color(Color::Muted)
1495                                    .buffer_font(cx),
1496                            ),
1497                    )
1498                    .child(
1499                        IconButton::new("open-rule", IconName::ArrowUpRightAlt)
1500                            .shape(ui::IconButtonShape::Square)
1501                            .icon_size(IconSize::XSmall)
1502                            .icon_color(Color::Ignored)
1503                            .on_click(cx.listener(Self::handle_open_rules))
1504                            .tooltip(Tooltip::text("View Rules")),
1505                    ),
1506            )
1507            .into_any()
1508    }
1509
1510    fn handle_allow_tool(
1511        &mut self,
1512        tool_use_id: LanguageModelToolUseId,
1513        _: &ClickEvent,
1514        _window: &mut Window,
1515        cx: &mut Context<Self>,
1516    ) {
1517        if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
1518            .thread
1519            .read(cx)
1520            .pending_tool(&tool_use_id)
1521            .map(|tool_use| tool_use.status.clone())
1522        {
1523            self.thread.update(cx, |thread, cx| {
1524                thread.run_tool(
1525                    c.tool_use_id.clone(),
1526                    c.ui_text.clone(),
1527                    c.input.clone(),
1528                    &c.messages,
1529                    c.tool.clone(),
1530                    cx,
1531                );
1532            });
1533        }
1534    }
1535
1536    fn handle_deny_tool(
1537        &mut self,
1538        tool_use_id: LanguageModelToolUseId,
1539        _: &ClickEvent,
1540        _window: &mut Window,
1541        cx: &mut Context<Self>,
1542    ) {
1543        self.thread.update(cx, |thread, cx| {
1544            thread.deny_tool_use(tool_use_id, cx);
1545        });
1546    }
1547
1548    fn handle_open_rules(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
1549        let Some(system_prompt_context) = self.thread.read(cx).system_prompt_context().as_ref()
1550        else {
1551            return;
1552        };
1553
1554        let abs_paths = system_prompt_context
1555            .worktrees
1556            .iter()
1557            .flat_map(|worktree| worktree.rules_file.as_ref())
1558            .map(|rules_file| rules_file.abs_path.to_path_buf())
1559            .collect::<Vec<_>>();
1560
1561        if let Ok(task) = self.workspace.update(cx, move |workspace, cx| {
1562            // TODO: Open a multibuffer instead? In some cases this doesn't make the set of rules
1563            // files clear. For example, if rules file 1 is already open but rules file 2 is not,
1564            // this would open and focus rules file 2 in a tab that is not next to rules file 1.
1565            workspace.open_paths(abs_paths, OpenOptions::default(), None, window, cx)
1566        }) {
1567            task.detach();
1568        }
1569    }
1570
1571    fn render_confirmations<'a>(
1572        &'a mut self,
1573        cx: &'a mut Context<Self>,
1574    ) -> impl Iterator<Item = AnyElement> + 'a {
1575        let thread = self.thread.read(cx);
1576
1577        thread
1578            .tools_needing_confirmation()
1579            .map(|tool| {
1580                div()
1581                    .m_3()
1582                    .p_2()
1583                    .bg(cx.theme().colors().editor_background)
1584                    .border_1()
1585                    .border_color(cx.theme().colors().border)
1586                    .rounded_lg()
1587                    .child(
1588                        v_flex()
1589                            .gap_1()
1590                            .child(
1591                                v_flex()
1592                                    .gap_0p5()
1593                                    .child(
1594                                        Label::new("The agent wants to run this action:")
1595                                            .color(Color::Muted),
1596                                    )
1597                                    .child(div().p_3().child(Label::new(&tool.ui_text))),
1598                            )
1599                            .child(
1600                                h_flex()
1601                                    .gap_1()
1602                                    .child({
1603                                        let tool_id = tool.id.clone();
1604                                        Button::new("allow-tool-action", "Allow").on_click(
1605                                            cx.listener(move |this, event, window, cx| {
1606                                                this.handle_allow_tool(
1607                                                    tool_id.clone(),
1608                                                    event,
1609                                                    window,
1610                                                    cx,
1611                                                )
1612                                            }),
1613                                        )
1614                                    })
1615                                    .child({
1616                                        let tool_id = tool.id.clone();
1617                                        Button::new("deny-tool", "Deny").on_click(cx.listener(
1618                                            move |this, event, window, cx| {
1619                                                this.handle_deny_tool(
1620                                                    tool_id.clone(),
1621                                                    event,
1622                                                    window,
1623                                                    cx,
1624                                                )
1625                                            },
1626                                        ))
1627                                    }),
1628                            )
1629                            .child(
1630                                Label::new("Note: A future release will introduce a way to remember your answers to these. In the meantime, you can avoid these prompts by adding \"assistant\": { \"always_allow_tool_actions\": true } to your settings.json.")
1631                                    .color(Color::Muted)
1632                                    .size(LabelSize::Small),
1633                            ),
1634                    )
1635                    .into_any()
1636            })
1637    }
1638}
1639
1640impl Render for ActiveThread {
1641    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1642        v_flex()
1643            .size_full()
1644            .child(list(self.list_state.clone()).flex_grow())
1645            .children(self.render_confirmations(cx))
1646    }
1647}