assistant_panel.rs

   1use crate::{
   2    assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
   3    codegen::{self, Codegen, CodegenKind},
   4    prompts::generate_content_prompt,
   5    Assist, CycleMessageRole, InlineAssist, MessageId, MessageMetadata, MessageStatus,
   6    NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
   7    SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
   8};
   9
  10use ai::{
  11    auth::ProviderCredential,
  12    completion::{CompletionProvider, CompletionRequest},
  13    providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
  14};
  15
  16use ai::prompts::repository_context::PromptCodeSnippet;
  17use anyhow::{anyhow, Result};
  18use chrono::{DateTime, Local};
  19use client::{telemetry::AssistantKind, TelemetrySettings};
  20use collections::{hash_map, HashMap, HashSet, VecDeque};
  21use editor::{
  22    display_map::{
  23        BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint,
  24    },
  25    scroll::autoscroll::{Autoscroll, AutoscrollStrategy},
  26    Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MoveDown, MoveUp, MultiBufferSnapshot,
  27    ToOffset, ToPoint,
  28};
  29use fs::Fs;
  30use futures::StreamExt;
  31use gpui::{
  32    div, point, relative, rems, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext,
  33    ClipboardItem, Context, Div, EventEmitter, FocusHandle, Focusable, FocusableView, FontStyle,
  34    FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext,
  35    ParentElement, Pixels, PromptLevel, Render, SharedString, StatefulInteractiveElement, Styled,
  36    Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
  37    WeakModel, WeakView, WhiteSpace, WindowContext,
  38};
  39use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
  40use project::Project;
  41use search::BufferSearchBar;
  42use semantic_index::{SemanticIndex, SemanticIndexStatus};
  43use settings::{Settings, SettingsStore};
  44use std::{
  45    cell::Cell,
  46    cmp,
  47    fmt::Write,
  48    iter,
  49    ops::Range,
  50    path::{Path, PathBuf},
  51    rc::Rc,
  52    sync::Arc,
  53    time::{Duration, Instant},
  54};
  55use theme::ThemeSettings;
  56use ui::{prelude::*, Tab, TabBar, Tooltip};
  57use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
  58use uuid::Uuid;
  59use workspace::{
  60    dock::{DockPosition, Panel, PanelEvent},
  61    searchable::Direction,
  62    Save, Toast, ToggleZoom, Toolbar, Workspace,
  63};
  64
  65pub fn init(cx: &mut AppContext) {
  66    AssistantSettings::register(cx);
  67    cx.observe_new_views(
  68        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
  69            workspace
  70                .register_action(|workspace, _: &ToggleFocus, cx| {
  71                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
  72                })
  73                .register_action(AssistantPanel::inline_assist)
  74                .register_action(AssistantPanel::cancel_last_inline_assist)
  75                .register_action(ConversationEditor::quote_selection);
  76        },
  77    )
  78    .detach();
  79}
  80
  81pub struct AssistantPanel {
  82    workspace: WeakView<Workspace>,
  83    width: Option<f32>,
  84    height: Option<f32>,
  85    active_editor_index: Option<usize>,
  86    prev_active_editor_index: Option<usize>,
  87    editors: Vec<View<ConversationEditor>>,
  88    saved_conversations: Vec<SavedConversationMetadata>,
  89    saved_conversations_scroll_handle: UniformListScrollHandle,
  90    zoomed: bool,
  91    focus_handle: FocusHandle,
  92    toolbar: View<Toolbar>,
  93    completion_provider: Arc<dyn CompletionProvider>,
  94    api_key_editor: Option<View<Editor>>,
  95    languages: Arc<LanguageRegistry>,
  96    fs: Arc<dyn Fs>,
  97    subscriptions: Vec<Subscription>,
  98    next_inline_assist_id: usize,
  99    pending_inline_assists: HashMap<usize, PendingInlineAssist>,
 100    pending_inline_assist_ids_by_editor: HashMap<WeakView<Editor>, Vec<usize>>,
 101    include_conversation_in_next_inline_assist: bool,
 102    inline_prompt_history: VecDeque<String>,
 103    _watch_saved_conversations: Task<Result<()>>,
 104    semantic_index: Option<Model<SemanticIndex>>,
 105    retrieve_context_in_next_inline_assist: bool,
 106}
 107
 108impl AssistantPanel {
 109    const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
 110
 111    pub fn load(
 112        workspace: WeakView<Workspace>,
 113        cx: AsyncWindowContext,
 114    ) -> Task<Result<View<Self>>> {
 115        cx.spawn(|mut cx| async move {
 116            let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
 117            let saved_conversations = SavedConversationMetadata::list(fs.clone())
 118                .await
 119                .log_err()
 120                .unwrap_or_default();
 121
 122            // TODO: deserialize state.
 123            let workspace_handle = workspace.clone();
 124            workspace.update(&mut cx, |workspace, cx| {
 125                cx.build_view::<Self>(|cx| {
 126                    const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
 127                    let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
 128                        let mut events = fs
 129                            .watch(&CONVERSATIONS_DIR, CONVERSATION_WATCH_DURATION)
 130                            .await;
 131                        while events.next().await.is_some() {
 132                            let saved_conversations = SavedConversationMetadata::list(fs.clone())
 133                                .await
 134                                .log_err()
 135                                .unwrap_or_default();
 136                            this.update(&mut cx, |this, cx| {
 137                                this.saved_conversations = saved_conversations;
 138                                cx.notify();
 139                            })
 140                            .ok();
 141                        }
 142
 143                        anyhow::Ok(())
 144                    });
 145
 146                    let toolbar = cx.build_view(|cx| {
 147                        let mut toolbar = Toolbar::new();
 148                        toolbar.set_can_navigate(false, cx);
 149                        toolbar.add_item(cx.build_view(|cx| BufferSearchBar::new(cx)), cx);
 150                        toolbar
 151                    });
 152
 153                    let semantic_index = SemanticIndex::global(cx);
 154                    // Defaulting currently to GPT4, allow for this to be set via config.
 155                    let completion_provider = Arc::new(OpenAICompletionProvider::new(
 156                        "gpt-4",
 157                        cx.background_executor().clone(),
 158                    ));
 159
 160                    let focus_handle = cx.focus_handle();
 161                    cx.on_focus_in(&focus_handle, Self::focus_in).detach();
 162                    cx.on_focus_out(&focus_handle, Self::focus_out).detach();
 163
 164                    let mut this = Self {
 165                        workspace: workspace_handle,
 166                        active_editor_index: Default::default(),
 167                        prev_active_editor_index: Default::default(),
 168                        editors: Default::default(),
 169                        saved_conversations,
 170                        saved_conversations_scroll_handle: Default::default(),
 171                        zoomed: false,
 172                        focus_handle,
 173                        toolbar,
 174                        completion_provider,
 175                        api_key_editor: None,
 176                        languages: workspace.app_state().languages.clone(),
 177                        fs: workspace.app_state().fs.clone(),
 178                        width: None,
 179                        height: None,
 180                        subscriptions: Default::default(),
 181                        next_inline_assist_id: 0,
 182                        pending_inline_assists: Default::default(),
 183                        pending_inline_assist_ids_by_editor: Default::default(),
 184                        include_conversation_in_next_inline_assist: false,
 185                        inline_prompt_history: Default::default(),
 186                        _watch_saved_conversations,
 187                        semantic_index,
 188                        retrieve_context_in_next_inline_assist: false,
 189                    };
 190
 191                    let mut old_dock_position = this.position(cx);
 192                    this.subscriptions =
 193                        vec![cx.observe_global::<SettingsStore>(move |this, cx| {
 194                            let new_dock_position = this.position(cx);
 195                            if new_dock_position != old_dock_position {
 196                                old_dock_position = new_dock_position;
 197                                cx.emit(PanelEvent::ChangePosition);
 198                            }
 199                            cx.notify();
 200                        })];
 201
 202                    this
 203                })
 204            })
 205        })
 206    }
 207
 208    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
 209        self.toolbar
 210            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
 211        cx.notify();
 212        if self.focus_handle.is_focused(cx) {
 213            if let Some(editor) = self.active_editor() {
 214                cx.focus_view(editor);
 215            } else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
 216                cx.focus_view(api_key_editor);
 217            }
 218        }
 219    }
 220
 221    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
 222        self.toolbar
 223            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
 224        cx.notify();
 225    }
 226
 227    pub fn inline_assist(
 228        workspace: &mut Workspace,
 229        _: &InlineAssist,
 230        cx: &mut ViewContext<Workspace>,
 231    ) {
 232        let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
 233            if this.update(cx, |assistant, cx| {
 234                if !assistant.has_credentials() {
 235                    assistant.load_credentials(cx);
 236                };
 237
 238                assistant.has_credentials()
 239            }) {
 240                this
 241            } else {
 242                workspace.focus_panel::<AssistantPanel>(cx);
 243                return;
 244            }
 245        } else {
 246            return;
 247        };
 248
 249        let active_editor = if let Some(active_editor) = workspace
 250            .active_item(cx)
 251            .and_then(|item| item.act_as::<Editor>(cx))
 252        {
 253            active_editor
 254        } else {
 255            return;
 256        };
 257
 258        let project = workspace.project();
 259
 260        this.update(cx, |assistant, cx| {
 261            assistant.new_inline_assist(&active_editor, cx, project)
 262        });
 263    }
 264
 265    fn new_inline_assist(
 266        &mut self,
 267        editor: &View<Editor>,
 268        cx: &mut ViewContext<Self>,
 269        project: &Model<Project>,
 270    ) {
 271        let selection = editor.read(cx).selections.newest_anchor().clone();
 272        if selection.start.excerpt_id != selection.end.excerpt_id {
 273            return;
 274        }
 275        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
 276
 277        // Extend the selection to the start and the end of the line.
 278        let mut point_selection = selection.map(|selection| selection.to_point(&snapshot));
 279        if point_selection.end > point_selection.start {
 280            point_selection.start.column = 0;
 281            // If the selection ends at the start of the line, we don't want to include it.
 282            if point_selection.end.column == 0 {
 283                point_selection.end.row -= 1;
 284            }
 285            point_selection.end.column = snapshot.line_len(point_selection.end.row);
 286        }
 287
 288        let codegen_kind = if point_selection.start == point_selection.end {
 289            CodegenKind::Generate {
 290                position: snapshot.anchor_after(point_selection.start),
 291            }
 292        } else {
 293            CodegenKind::Transform {
 294                range: snapshot.anchor_before(point_selection.start)
 295                    ..snapshot.anchor_after(point_selection.end),
 296            }
 297        };
 298
 299        let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
 300        let provider = self.completion_provider.clone();
 301
 302        // Retrieve Credentials Authenticates the Provider
 303        provider.retrieve_credentials(cx);
 304
 305        let codegen = cx.build_model(|cx| {
 306            Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
 307        });
 308
 309        if let Some(semantic_index) = self.semantic_index.clone() {
 310            let project = project.clone();
 311            cx.spawn(|_, mut cx| async move {
 312                let previously_indexed = semantic_index
 313                    .update(&mut cx, |index, cx| {
 314                        index.project_previously_indexed(&project, cx)
 315                    })?
 316                    .await
 317                    .unwrap_or(false);
 318                if previously_indexed {
 319                    let _ = semantic_index
 320                        .update(&mut cx, |index, cx| {
 321                            index.index_project(project.clone(), cx)
 322                        })?
 323                        .await;
 324                }
 325                anyhow::Ok(())
 326            })
 327            .detach_and_log_err(cx);
 328        }
 329
 330        let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
 331        let inline_assistant = cx.build_view(|cx| {
 332            InlineAssistant::new(
 333                inline_assist_id,
 334                measurements.clone(),
 335                self.include_conversation_in_next_inline_assist,
 336                self.inline_prompt_history.clone(),
 337                codegen.clone(),
 338                self.workspace.clone(),
 339                cx,
 340                self.retrieve_context_in_next_inline_assist,
 341                self.semantic_index.clone(),
 342                project.clone(),
 343            )
 344        });
 345        let block_id = editor.update(cx, |editor, cx| {
 346            editor.change_selections(None, cx, |selections| {
 347                selections.select_anchor_ranges([selection.head()..selection.head()])
 348            });
 349            editor.insert_blocks(
 350                [BlockProperties {
 351                    style: BlockStyle::Flex,
 352                    position: snapshot.anchor_before(point_selection.head()),
 353                    height: 2,
 354                    render: Arc::new({
 355                        let inline_assistant = inline_assistant.clone();
 356                        move |cx: &mut BlockContext| {
 357                            measurements.set(BlockMeasurements {
 358                                anchor_x: cx.anchor_x,
 359                                gutter_width: cx.gutter_width,
 360                            });
 361                            inline_assistant.clone().into_any_element()
 362                        }
 363                    }),
 364                    disposition: if selection.reversed {
 365                        BlockDisposition::Above
 366                    } else {
 367                        BlockDisposition::Below
 368                    },
 369                }],
 370                Some(Autoscroll::Strategy(AutoscrollStrategy::Newest)),
 371                cx,
 372            )[0]
 373        });
 374
 375        self.pending_inline_assists.insert(
 376            inline_assist_id,
 377            PendingInlineAssist {
 378                editor: editor.downgrade(),
 379                inline_assistant: Some((block_id, inline_assistant.clone())),
 380                codegen: codegen.clone(),
 381                project: project.downgrade(),
 382                _subscriptions: vec![
 383                    cx.subscribe(&inline_assistant, Self::handle_inline_assistant_event),
 384                    cx.subscribe(editor, {
 385                        let inline_assistant = inline_assistant.downgrade();
 386                        move |_, editor, event, cx| {
 387                            if let Some(inline_assistant) = inline_assistant.upgrade() {
 388                                if let EditorEvent::SelectionsChanged { local } = event {
 389                                    if *local
 390                                        && inline_assistant.focus_handle(cx).contains_focused(cx)
 391                                    {
 392                                        cx.focus_view(&editor);
 393                                    }
 394                                }
 395                            }
 396                        }
 397                    }),
 398                    cx.observe(&codegen, {
 399                        let editor = editor.downgrade();
 400                        move |this, _, cx| {
 401                            if let Some(editor) = editor.upgrade() {
 402                                this.update_highlights_for_editor(&editor, cx);
 403                            }
 404                        }
 405                    }),
 406                    cx.subscribe(&codegen, move |this, codegen, event, cx| match event {
 407                        codegen::Event::Undone => {
 408                            this.finish_inline_assist(inline_assist_id, false, cx)
 409                        }
 410                        codegen::Event::Finished => {
 411                            let pending_assist = if let Some(pending_assist) =
 412                                this.pending_inline_assists.get(&inline_assist_id)
 413                            {
 414                                pending_assist
 415                            } else {
 416                                return;
 417                            };
 418
 419                            let error = codegen
 420                                .read(cx)
 421                                .error()
 422                                .map(|error| format!("Inline assistant error: {}", error));
 423                            if let Some(error) = error {
 424                                if pending_assist.inline_assistant.is_none() {
 425                                    if let Some(workspace) = this.workspace.upgrade() {
 426                                        workspace.update(cx, |workspace, cx| {
 427                                            workspace.show_toast(
 428                                                Toast::new(inline_assist_id, error),
 429                                                cx,
 430                                            );
 431                                        })
 432                                    }
 433
 434                                    this.finish_inline_assist(inline_assist_id, false, cx);
 435                                }
 436                            } else {
 437                                this.finish_inline_assist(inline_assist_id, false, cx);
 438                            }
 439                        }
 440                    }),
 441                ],
 442            },
 443        );
 444        self.pending_inline_assist_ids_by_editor
 445            .entry(editor.downgrade())
 446            .or_default()
 447            .push(inline_assist_id);
 448        self.update_highlights_for_editor(&editor, cx);
 449    }
 450
 451    fn handle_inline_assistant_event(
 452        &mut self,
 453        inline_assistant: View<InlineAssistant>,
 454        event: &InlineAssistantEvent,
 455        cx: &mut ViewContext<Self>,
 456    ) {
 457        let assist_id = inline_assistant.read(cx).id;
 458        match event {
 459            InlineAssistantEvent::Confirmed {
 460                prompt,
 461                include_conversation,
 462                retrieve_context,
 463            } => {
 464                self.confirm_inline_assist(
 465                    assist_id,
 466                    prompt,
 467                    *include_conversation,
 468                    cx,
 469                    *retrieve_context,
 470                );
 471            }
 472            InlineAssistantEvent::Canceled => {
 473                self.finish_inline_assist(assist_id, true, cx);
 474            }
 475            InlineAssistantEvent::Dismissed => {
 476                self.hide_inline_assist(assist_id, cx);
 477            }
 478            InlineAssistantEvent::IncludeConversationToggled {
 479                include_conversation,
 480            } => {
 481                self.include_conversation_in_next_inline_assist = *include_conversation;
 482            }
 483            InlineAssistantEvent::RetrieveContextToggled { retrieve_context } => {
 484                self.retrieve_context_in_next_inline_assist = *retrieve_context
 485            }
 486        }
 487    }
 488
 489    fn cancel_last_inline_assist(
 490        workspace: &mut Workspace,
 491        _: &editor::Cancel,
 492        cx: &mut ViewContext<Workspace>,
 493    ) {
 494        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 495            if let Some(editor) = workspace
 496                .active_item(cx)
 497                .and_then(|item| item.downcast::<Editor>())
 498            {
 499                let handled = panel.update(cx, |panel, cx| {
 500                    if let Some(assist_id) = panel
 501                        .pending_inline_assist_ids_by_editor
 502                        .get(&editor.downgrade())
 503                        .and_then(|assist_ids| assist_ids.last().copied())
 504                    {
 505                        panel.finish_inline_assist(assist_id, true, cx);
 506                        true
 507                    } else {
 508                        false
 509                    }
 510                });
 511                if handled {
 512                    return;
 513                }
 514            }
 515        }
 516
 517        cx.propagate();
 518    }
 519
 520    fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
 521        self.hide_inline_assist(assist_id, cx);
 522
 523        if let Some(pending_assist) = self.pending_inline_assists.remove(&assist_id) {
 524            if let hash_map::Entry::Occupied(mut entry) = self
 525                .pending_inline_assist_ids_by_editor
 526                .entry(pending_assist.editor.clone())
 527            {
 528                entry.get_mut().retain(|id| *id != assist_id);
 529                if entry.get().is_empty() {
 530                    entry.remove();
 531                }
 532            }
 533
 534            if let Some(editor) = pending_assist.editor.upgrade() {
 535                self.update_highlights_for_editor(&editor, cx);
 536
 537                if undo {
 538                    pending_assist
 539                        .codegen
 540                        .update(cx, |codegen, cx| codegen.undo(cx));
 541                }
 542            }
 543        }
 544    }
 545
 546    fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
 547        if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
 548            if let Some(editor) = pending_assist.editor.upgrade() {
 549                if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() {
 550                    editor.update(cx, |editor, cx| {
 551                        editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
 552                        if inline_assistant.focus_handle(cx).contains_focused(cx) {
 553                            editor.focus(cx);
 554                        }
 555                    });
 556                }
 557            }
 558        }
 559    }
 560
 561    fn confirm_inline_assist(
 562        &mut self,
 563        inline_assist_id: usize,
 564        user_prompt: &str,
 565        include_conversation: bool,
 566        cx: &mut ViewContext<Self>,
 567        retrieve_context: bool,
 568    ) {
 569        let conversation = if include_conversation {
 570            self.active_editor()
 571                .map(|editor| editor.read(cx).conversation.clone())
 572        } else {
 573            None
 574        };
 575
 576        let pending_assist =
 577            if let Some(pending_assist) = self.pending_inline_assists.get_mut(&inline_assist_id) {
 578                pending_assist
 579            } else {
 580                return;
 581            };
 582
 583        let editor = if let Some(editor) = pending_assist.editor.upgrade() {
 584            editor
 585        } else {
 586            return;
 587        };
 588
 589        let project = pending_assist.project.clone();
 590
 591        let project_name = if let Some(project) = project.upgrade() {
 592            Some(
 593                project
 594                    .read(cx)
 595                    .worktree_root_names(cx)
 596                    .collect::<Vec<&str>>()
 597                    .join("/"),
 598            )
 599        } else {
 600            None
 601        };
 602
 603        self.inline_prompt_history
 604            .retain(|prompt| prompt != user_prompt);
 605        self.inline_prompt_history.push_back(user_prompt.into());
 606        if self.inline_prompt_history.len() > Self::INLINE_PROMPT_HISTORY_MAX_LEN {
 607            self.inline_prompt_history.pop_front();
 608        }
 609
 610        let codegen = pending_assist.codegen.clone();
 611        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
 612        let range = codegen.read(cx).range();
 613        let start = snapshot.point_to_buffer_offset(range.start);
 614        let end = snapshot.point_to_buffer_offset(range.end);
 615        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
 616            let (start_buffer, start_buffer_offset) = start;
 617            let (end_buffer, end_buffer_offset) = end;
 618            if start_buffer.remote_id() == end_buffer.remote_id() {
 619                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
 620            } else {
 621                self.finish_inline_assist(inline_assist_id, false, cx);
 622                return;
 623            }
 624        } else {
 625            self.finish_inline_assist(inline_assist_id, false, cx);
 626            return;
 627        };
 628
 629        let language = buffer.language_at(range.start);
 630        let language_name = if let Some(language) = language.as_ref() {
 631            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
 632                None
 633            } else {
 634                Some(language.name())
 635            }
 636        } else {
 637            None
 638        };
 639
 640        // Higher Temperature increases the randomness of model outputs.
 641        // If Markdown or No Language is Known, increase the randomness for more creative output
 642        // If Code, decrease temperature to get more deterministic outputs
 643        let temperature = if let Some(language) = language_name.clone() {
 644            if language.to_string() != "Markdown".to_string() {
 645                0.5
 646            } else {
 647                1.0
 648            }
 649        } else {
 650            1.0
 651        };
 652
 653        let user_prompt = user_prompt.to_string();
 654
 655        let snippets = if retrieve_context {
 656            let Some(project) = project.upgrade() else {
 657                return;
 658            };
 659
 660            let search_results = if let Some(semantic_index) = self.semantic_index.clone() {
 661                let search_results = semantic_index.update(cx, |this, cx| {
 662                    this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx)
 663                });
 664
 665                cx.background_executor()
 666                    .spawn(async move { search_results.await.unwrap_or_default() })
 667            } else {
 668                Task::ready(Vec::new())
 669            };
 670
 671            let snippets = cx.spawn(|_, mut cx| async move {
 672                let mut snippets = Vec::new();
 673                for result in search_results.await {
 674                    snippets.push(PromptCodeSnippet::new(
 675                        result.buffer,
 676                        result.range,
 677                        &mut cx,
 678                    )?);
 679                }
 680                anyhow::Ok(snippets)
 681            });
 682            snippets
 683        } else {
 684            Task::ready(Ok(Vec::new()))
 685        };
 686
 687        let mut model = AssistantSettings::get_global(cx)
 688            .default_open_ai_model
 689            .clone();
 690        let model_name = model.full_name();
 691
 692        let prompt = cx.background_executor().spawn(async move {
 693            let snippets = snippets.await?;
 694
 695            let language_name = language_name.as_deref();
 696            generate_content_prompt(
 697                user_prompt,
 698                language_name,
 699                buffer,
 700                range,
 701                snippets,
 702                model_name,
 703                project_name,
 704            )
 705        });
 706
 707        let mut messages = Vec::new();
 708        if let Some(conversation) = conversation {
 709            let conversation = conversation.read(cx);
 710            let buffer = conversation.buffer.read(cx);
 711            messages.extend(
 712                conversation
 713                    .messages(cx)
 714                    .map(|message| message.to_open_ai_message(buffer)),
 715            );
 716            model = conversation.model.clone();
 717        }
 718
 719        cx.spawn(|_, mut cx| async move {
 720            // I Don't know if we want to return a ? here.
 721            let prompt = prompt.await?;
 722
 723            messages.push(RequestMessage {
 724                role: Role::User,
 725                content: prompt,
 726            });
 727
 728            let request = Box::new(OpenAIRequest {
 729                model: model.full_name().into(),
 730                messages,
 731                stream: true,
 732                stop: vec!["|END|>".to_string()],
 733                temperature,
 734            });
 735
 736            codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
 737            anyhow::Ok(())
 738        })
 739        .detach();
 740    }
 741
 742    fn update_highlights_for_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Self>) {
 743        let mut background_ranges = Vec::new();
 744        let mut foreground_ranges = Vec::new();
 745        let empty_inline_assist_ids = Vec::new();
 746        let inline_assist_ids = self
 747            .pending_inline_assist_ids_by_editor
 748            .get(&editor.downgrade())
 749            .unwrap_or(&empty_inline_assist_ids);
 750
 751        for inline_assist_id in inline_assist_ids {
 752            if let Some(pending_assist) = self.pending_inline_assists.get(inline_assist_id) {
 753                let codegen = pending_assist.codegen.read(cx);
 754                background_ranges.push(codegen.range());
 755                foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
 756            }
 757        }
 758
 759        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
 760        merge_ranges(&mut background_ranges, &snapshot);
 761        merge_ranges(&mut foreground_ranges, &snapshot);
 762        editor.update(cx, |editor, cx| {
 763            if background_ranges.is_empty() {
 764                editor.clear_background_highlights::<PendingInlineAssist>(cx);
 765            } else {
 766                editor.highlight_background::<PendingInlineAssist>(
 767                    background_ranges,
 768                    |theme| theme.editor_active_line_background, // todo!("use the appropriate color")
 769                    cx,
 770                );
 771            }
 772
 773            if foreground_ranges.is_empty() {
 774                editor.clear_highlights::<PendingInlineAssist>(cx);
 775            } else {
 776                editor.highlight_text::<PendingInlineAssist>(
 777                    foreground_ranges,
 778                    HighlightStyle {
 779                        fade_out: Some(0.6),
 780                        ..Default::default()
 781                    },
 782                    cx,
 783                );
 784            }
 785        });
 786    }
 787
 788    fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
 789        let editor = cx.build_view(|cx| {
 790            ConversationEditor::new(
 791                self.completion_provider.clone(),
 792                self.languages.clone(),
 793                self.fs.clone(),
 794                self.workspace.clone(),
 795                cx,
 796            )
 797        });
 798        self.add_conversation(editor.clone(), cx);
 799        editor
 800    }
 801
 802    fn add_conversation(&mut self, editor: View<ConversationEditor>, cx: &mut ViewContext<Self>) {
 803        self.subscriptions
 804            .push(cx.subscribe(&editor, Self::handle_conversation_editor_event));
 805
 806        let conversation = editor.read(cx).conversation.clone();
 807        self.subscriptions
 808            .push(cx.observe(&conversation, |_, _, cx| cx.notify()));
 809
 810        let index = self.editors.len();
 811        self.editors.push(editor);
 812        self.set_active_editor_index(Some(index), cx);
 813    }
 814
 815    fn set_active_editor_index(&mut self, index: Option<usize>, cx: &mut ViewContext<Self>) {
 816        self.prev_active_editor_index = self.active_editor_index;
 817        self.active_editor_index = index;
 818        if let Some(editor) = self.active_editor() {
 819            let editor = editor.read(cx).editor.clone();
 820            self.toolbar.update(cx, |toolbar, cx| {
 821                toolbar.set_active_item(Some(&editor), cx);
 822            });
 823            if self.focus_handle.contains_focused(cx) {
 824                cx.focus_view(&editor);
 825            }
 826        } else {
 827            self.toolbar.update(cx, |toolbar, cx| {
 828                toolbar.set_active_item(None, cx);
 829            });
 830        }
 831
 832        cx.notify();
 833    }
 834
 835    fn handle_conversation_editor_event(
 836        &mut self,
 837        _: View<ConversationEditor>,
 838        event: &ConversationEditorEvent,
 839        cx: &mut ViewContext<Self>,
 840    ) {
 841        match event {
 842            ConversationEditorEvent::TabContentChanged => cx.notify(),
 843        }
 844    }
 845
 846    fn save_credentials(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
 847        if let Some(api_key) = self
 848            .api_key_editor
 849            .as_ref()
 850            .map(|editor| editor.read(cx).text(cx))
 851        {
 852            if !api_key.is_empty() {
 853                let credential = ProviderCredential::Credentials {
 854                    api_key: api_key.clone(),
 855                };
 856
 857                self.completion_provider.save_credentials(cx, credential);
 858
 859                self.api_key_editor.take();
 860                self.focus_handle.focus(cx);
 861                cx.notify();
 862            }
 863        } else {
 864            cx.propagate();
 865        }
 866    }
 867
 868    fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
 869        self.completion_provider.delete_credentials(cx);
 870        self.api_key_editor = Some(build_api_key_editor(cx));
 871        self.focus_handle.focus(cx);
 872        cx.notify();
 873    }
 874
 875    fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
 876        if self.zoomed {
 877            cx.emit(PanelEvent::ZoomOut)
 878        } else {
 879            cx.emit(PanelEvent::ZoomIn)
 880        }
 881    }
 882
 883    fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
 884        let mut propagate = true;
 885        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 886            search_bar.update(cx, |search_bar, cx| {
 887                if search_bar.show(cx) {
 888                    search_bar.search_suggested(cx);
 889                    if action.focus {
 890                        let focus_handle = search_bar.focus_handle(cx);
 891                        search_bar.select_query(cx);
 892                        cx.focus(&focus_handle);
 893                    }
 894                    propagate = false
 895                }
 896            });
 897        }
 898        if propagate {
 899            cx.propagate();
 900        }
 901    }
 902
 903    fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
 904        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 905            if !search_bar.read(cx).is_dismissed() {
 906                search_bar.update(cx, |search_bar, cx| {
 907                    search_bar.dismiss(&Default::default(), cx)
 908                });
 909                return;
 910            }
 911        }
 912        cx.propagate();
 913    }
 914
 915    fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
 916        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 917            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
 918        }
 919    }
 920
 921    fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
 922        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 923            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
 924        }
 925    }
 926
 927    fn active_editor(&self) -> Option<&View<ConversationEditor>> {
 928        self.editors.get(self.active_editor_index?)
 929    }
 930
 931    fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
 932        IconButton::new("hamburger_button", Icon::Menu)
 933            .on_click(cx.listener(|this, _event, cx| {
 934                if this.active_editor().is_some() {
 935                    this.set_active_editor_index(None, cx);
 936                } else {
 937                    this.set_active_editor_index(this.prev_active_editor_index, cx);
 938                }
 939            }))
 940            .tooltip(|cx| Tooltip::text("Conversation History", cx))
 941    }
 942
 943    fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
 944        if self.active_editor().is_some() {
 945            vec![
 946                Self::render_split_button(cx).into_any_element(),
 947                Self::render_quote_button(cx).into_any_element(),
 948                Self::render_assist_button(cx).into_any_element(),
 949            ]
 950        } else {
 951            Default::default()
 952        }
 953    }
 954
 955    fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
 956        IconButton::new("split_button", Icon::Snip)
 957            .on_click(cx.listener(|this, _event, cx| {
 958                if let Some(active_editor) = this.active_editor() {
 959                    active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
 960                }
 961            }))
 962            .icon_size(IconSize::Small)
 963            .tooltip(|cx| Tooltip::for_action("Split Message", &Split, cx))
 964    }
 965
 966    fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
 967        IconButton::new("assist_button", Icon::MagicWand)
 968            .on_click(cx.listener(|this, _event, cx| {
 969                if let Some(active_editor) = this.active_editor() {
 970                    active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
 971                }
 972            }))
 973            .icon_size(IconSize::Small)
 974            .tooltip(|cx| Tooltip::for_action("Assist", &Assist, cx))
 975    }
 976
 977    fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
 978        IconButton::new("quote_button", Icon::Quote)
 979            .on_click(cx.listener(|this, _event, cx| {
 980                if let Some(workspace) = this.workspace.upgrade() {
 981                    cx.window_context().defer(move |cx| {
 982                        workspace.update(cx, |workspace, cx| {
 983                            ConversationEditor::quote_selection(workspace, &Default::default(), cx)
 984                        });
 985                    });
 986                }
 987            }))
 988            .icon_size(IconSize::Small)
 989            .tooltip(|cx| Tooltip::for_action("Quote Seleciton", &QuoteSelection, cx))
 990    }
 991
 992    fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
 993        IconButton::new("plus_button", Icon::Plus)
 994            .on_click(cx.listener(|this, _event, cx| {
 995                this.new_conversation(cx);
 996            }))
 997            .icon_size(IconSize::Small)
 998            .tooltip(|cx| Tooltip::for_action("New Conversation", &NewConversation, cx))
 999    }
1000
1001    fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1002        let zoomed = self.zoomed;
1003        IconButton::new("zoom_button", Icon::Maximize)
1004            .on_click(cx.listener(|this, _event, cx| {
1005                this.toggle_zoom(&ToggleZoom, cx);
1006            }))
1007            .selected(zoomed)
1008            .selected_icon(Icon::Minimize)
1009            .icon_size(IconSize::Small)
1010            .tooltip(move |cx| {
1011                Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
1012            })
1013    }
1014
1015    fn render_saved_conversation(
1016        &mut self,
1017        index: usize,
1018        cx: &mut ViewContext<Self>,
1019    ) -> impl IntoElement {
1020        let conversation = &self.saved_conversations[index];
1021        let path = conversation.path.clone();
1022
1023        ButtonLike::new(index)
1024            .on_click(cx.listener(move |this, _, cx| {
1025                this.open_conversation(path.clone(), cx)
1026                    .detach_and_log_err(cx)
1027            }))
1028            .full_width()
1029            .child(
1030                div()
1031                    .flex()
1032                    .w_full()
1033                    .gap_2()
1034                    .child(
1035                        Label::new(conversation.mtime.format("%F %I:%M%p").to_string())
1036                            .color(Color::Muted)
1037                            .size(LabelSize::Small),
1038                    )
1039                    .child(Label::new(conversation.title.clone()).size(LabelSize::Small)),
1040            )
1041    }
1042
1043    fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
1044        cx.focus(&self.focus_handle);
1045
1046        if let Some(ix) = self.editor_index_for_path(&path, cx) {
1047            self.set_active_editor_index(Some(ix), cx);
1048            return Task::ready(Ok(()));
1049        }
1050
1051        let fs = self.fs.clone();
1052        let workspace = self.workspace.clone();
1053        let languages = self.languages.clone();
1054        cx.spawn(|this, mut cx| async move {
1055            let saved_conversation = fs.load(&path).await?;
1056            let saved_conversation = serde_json::from_str(&saved_conversation)?;
1057            let conversation = cx.build_model(|cx| {
1058                Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
1059            })?;
1060            this.update(&mut cx, |this, cx| {
1061                // If, by the time we've loaded the conversation, the user has already opened
1062                // the same conversation, we don't want to open it again.
1063                if let Some(ix) = this.editor_index_for_path(&path, cx) {
1064                    this.set_active_editor_index(Some(ix), cx);
1065                } else {
1066                    let editor = cx.build_view(|cx| {
1067                        ConversationEditor::for_conversation(conversation, fs, workspace, cx)
1068                    });
1069                    this.add_conversation(editor, cx);
1070                }
1071            })?;
1072            Ok(())
1073        })
1074    }
1075
1076    fn editor_index_for_path(&self, path: &Path, cx: &AppContext) -> Option<usize> {
1077        self.editors
1078            .iter()
1079            .position(|editor| editor.read(cx).conversation.read(cx).path.as_deref() == Some(path))
1080    }
1081
1082    fn has_credentials(&mut self) -> bool {
1083        self.completion_provider.has_credentials()
1084    }
1085
1086    fn load_credentials(&mut self, cx: &mut ViewContext<Self>) {
1087        self.completion_provider.retrieve_credentials(cx);
1088    }
1089}
1090
1091fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
1092    cx.build_view(|cx| {
1093        let mut editor = Editor::single_line(cx);
1094        editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
1095        editor
1096    })
1097}
1098
1099impl Render for AssistantPanel {
1100    type Element = Focusable<Div>;
1101
1102    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
1103        if let Some(api_key_editor) = self.api_key_editor.clone() {
1104            v_stack()
1105                .on_action(cx.listener(AssistantPanel::save_credentials))
1106                .track_focus(&self.focus_handle)
1107                .child(Label::new(
1108                    "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
1109                ))
1110                .child(Label::new(
1111                    " - Having a subscription for another service like GitHub Copilot won't work."
1112                ))
1113                .child(Label::new(
1114                    " - You can create a api key at: platform.openai.com/api-keys"
1115                ))
1116                .child(Label::new(
1117                    " "
1118                ))
1119                .child(Label::new(
1120                    "Paste your OpenAI API key and press Enter to use the assistant"
1121                ))
1122                .child(api_key_editor)
1123                .child(Label::new(
1124                    "Click on the Z button in the status bar to close this panel."
1125                ))
1126                .border()
1127                .border_color(gpui::red())
1128        } else {
1129            let header = ui::TabBar::new("assistant_header")
1130                .start_child(
1131                    h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title),
1132                )
1133                .children(self.active_editor().map(|editor| {
1134                    h_stack()
1135                        .h(rems(ui::Tab::HEIGHT_IN_REMS))
1136                        .flex_1()
1137                        .px_2()
1138                        .child(Label::new(editor.read(cx).title(cx)).into_element())
1139                }))
1140                .end_child(if self.focus_handle.contains_focused(cx) {
1141                    h_stack()
1142                        .gap_2()
1143                        .child(h_stack().gap_1().children(self.render_editor_tools(cx)))
1144                        .child(
1145                            ui::Divider::vertical()
1146                                .inset()
1147                                .color(ui::DividerColor::Border),
1148                        )
1149                        .child(
1150                            h_stack()
1151                                .gap_1()
1152                                .child(Self::render_plus_button(cx))
1153                                .child(self.render_zoom_button(cx)),
1154                        )
1155                } else {
1156                    div()
1157                });
1158
1159            v_stack()
1160                .size_full()
1161                .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
1162                    this.new_conversation(cx);
1163                }))
1164                .on_action(cx.listener(AssistantPanel::reset_credentials))
1165                .on_action(cx.listener(AssistantPanel::toggle_zoom))
1166                .on_action(cx.listener(AssistantPanel::deploy))
1167                .on_action(cx.listener(AssistantPanel::select_next_match))
1168                .on_action(cx.listener(AssistantPanel::select_prev_match))
1169                .on_action(cx.listener(AssistantPanel::handle_editor_cancel))
1170                .track_focus(&self.focus_handle)
1171                .child(header)
1172                .children(if self.toolbar.read(cx).hidden() {
1173                    None
1174                } else {
1175                    Some(self.toolbar.clone())
1176                })
1177                .child(
1178                    div()
1179                        .flex_1()
1180                        .child(if let Some(editor) = self.active_editor() {
1181                            editor.clone().into_any_element()
1182                        } else {
1183                            uniform_list(
1184                                cx.view().clone(),
1185                                "saved_conversations",
1186                                self.saved_conversations.len(),
1187                                |this, range, cx| {
1188                                    range
1189                                        .map(|ix| this.render_saved_conversation(ix, cx))
1190                                        .collect()
1191                                },
1192                            )
1193                            .track_scroll(self.saved_conversations_scroll_handle.clone())
1194                            .into_any_element()
1195                        }),
1196                )
1197        }
1198    }
1199}
1200
1201impl Panel for AssistantPanel {
1202    fn persistent_name() -> &'static str {
1203        "AssistantPanel"
1204    }
1205
1206    fn position(&self, cx: &WindowContext) -> DockPosition {
1207        match AssistantSettings::get_global(cx).dock {
1208            AssistantDockPosition::Left => DockPosition::Left,
1209            AssistantDockPosition::Bottom => DockPosition::Bottom,
1210            AssistantDockPosition::Right => DockPosition::Right,
1211        }
1212    }
1213
1214    fn position_is_valid(&self, _: DockPosition) -> bool {
1215        true
1216    }
1217
1218    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1219        settings::update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings| {
1220            let dock = match position {
1221                DockPosition::Left => AssistantDockPosition::Left,
1222                DockPosition::Bottom => AssistantDockPosition::Bottom,
1223                DockPosition::Right => AssistantDockPosition::Right,
1224            };
1225            settings.dock = Some(dock);
1226        });
1227    }
1228
1229    fn size(&self, cx: &WindowContext) -> f32 {
1230        let settings = AssistantSettings::get_global(cx);
1231        match self.position(cx) {
1232            DockPosition::Left | DockPosition::Right => {
1233                self.width.unwrap_or_else(|| settings.default_width)
1234            }
1235            DockPosition::Bottom => self.height.unwrap_or_else(|| settings.default_height),
1236        }
1237    }
1238
1239    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
1240        match self.position(cx) {
1241            DockPosition::Left | DockPosition::Right => self.width = size,
1242            DockPosition::Bottom => self.height = size,
1243        }
1244        cx.notify();
1245    }
1246
1247    fn is_zoomed(&self, _: &WindowContext) -> bool {
1248        self.zoomed
1249    }
1250
1251    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1252        self.zoomed = zoomed;
1253        cx.notify();
1254    }
1255
1256    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1257        if active {
1258            self.load_credentials(cx);
1259
1260            if self.editors.is_empty() {
1261                self.new_conversation(cx);
1262            }
1263        }
1264    }
1265
1266    fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
1267        Some(Icon::Ai)
1268    }
1269
1270    fn toggle_action(&self) -> Box<dyn Action> {
1271        Box::new(ToggleFocus)
1272    }
1273}
1274
1275impl EventEmitter<PanelEvent> for AssistantPanel {}
1276
1277impl FocusableView for AssistantPanel {
1278    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
1279        self.focus_handle.clone()
1280    }
1281}
1282
1283enum ConversationEvent {
1284    MessagesEdited,
1285    SummaryChanged,
1286    StreamedCompletion,
1287}
1288
1289#[derive(Default)]
1290struct Summary {
1291    text: String,
1292    done: bool,
1293}
1294
1295struct Conversation {
1296    id: Option<String>,
1297    buffer: Model<Buffer>,
1298    message_anchors: Vec<MessageAnchor>,
1299    messages_metadata: HashMap<MessageId, MessageMetadata>,
1300    next_message_id: MessageId,
1301    summary: Option<Summary>,
1302    pending_summary: Task<Option<()>>,
1303    completion_count: usize,
1304    pending_completions: Vec<PendingCompletion>,
1305    model: OpenAIModel,
1306    token_count: Option<usize>,
1307    max_token_count: usize,
1308    pending_token_count: Task<Option<()>>,
1309    pending_save: Task<Result<()>>,
1310    path: Option<PathBuf>,
1311    _subscriptions: Vec<Subscription>,
1312    completion_provider: Arc<dyn CompletionProvider>,
1313}
1314
1315impl EventEmitter<ConversationEvent> for Conversation {}
1316
1317impl Conversation {
1318    fn new(
1319        language_registry: Arc<LanguageRegistry>,
1320        cx: &mut ModelContext<Self>,
1321        completion_provider: Arc<dyn CompletionProvider>,
1322    ) -> Self {
1323        let markdown = language_registry.language_for_name("Markdown");
1324        let buffer = cx.build_model(|cx| {
1325            let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "");
1326            buffer.set_language_registry(language_registry);
1327            cx.spawn(|buffer, mut cx| async move {
1328                let markdown = markdown.await?;
1329                buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
1330                    buffer.set_language(Some(markdown), cx)
1331                })?;
1332                anyhow::Ok(())
1333            })
1334            .detach_and_log_err(cx);
1335            buffer
1336        });
1337
1338        let settings = AssistantSettings::get_global(cx);
1339        let model = settings.default_open_ai_model.clone();
1340
1341        let mut this = Self {
1342            id: Some(Uuid::new_v4().to_string()),
1343            message_anchors: Default::default(),
1344            messages_metadata: Default::default(),
1345            next_message_id: Default::default(),
1346            summary: None,
1347            pending_summary: Task::ready(None),
1348            completion_count: Default::default(),
1349            pending_completions: Default::default(),
1350            token_count: None,
1351            max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
1352            pending_token_count: Task::ready(None),
1353            model: model.clone(),
1354            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1355            pending_save: Task::ready(Ok(())),
1356            path: None,
1357            buffer,
1358            completion_provider,
1359        };
1360        let message = MessageAnchor {
1361            id: MessageId(post_inc(&mut this.next_message_id.0)),
1362            start: language::Anchor::MIN,
1363        };
1364        this.message_anchors.push(message.clone());
1365        this.messages_metadata.insert(
1366            message.id,
1367            MessageMetadata {
1368                role: Role::User,
1369                sent_at: Local::now(),
1370                status: MessageStatus::Done,
1371            },
1372        );
1373
1374        this.count_remaining_tokens(cx);
1375        this
1376    }
1377
1378    fn serialize(&self, cx: &AppContext) -> SavedConversation {
1379        SavedConversation {
1380            id: self.id.clone(),
1381            zed: "conversation".into(),
1382            version: SavedConversation::VERSION.into(),
1383            text: self.buffer.read(cx).text(),
1384            message_metadata: self.messages_metadata.clone(),
1385            messages: self
1386                .messages(cx)
1387                .map(|message| SavedMessage {
1388                    id: message.id,
1389                    start: message.offset_range.start,
1390                })
1391                .collect(),
1392            summary: self
1393                .summary
1394                .as_ref()
1395                .map(|summary| summary.text.clone())
1396                .unwrap_or_default(),
1397            model: self.model.clone(),
1398        }
1399    }
1400
1401    fn deserialize(
1402        saved_conversation: SavedConversation,
1403        path: PathBuf,
1404        language_registry: Arc<LanguageRegistry>,
1405        cx: &mut ModelContext<Self>,
1406    ) -> Self {
1407        let id = match saved_conversation.id {
1408            Some(id) => Some(id),
1409            None => Some(Uuid::new_v4().to_string()),
1410        };
1411        let model = saved_conversation.model;
1412        let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
1413            OpenAICompletionProvider::new(model.full_name(), cx.background_executor().clone()),
1414        );
1415        completion_provider.retrieve_credentials(cx);
1416        let markdown = language_registry.language_for_name("Markdown");
1417        let mut message_anchors = Vec::new();
1418        let mut next_message_id = MessageId(0);
1419        let buffer = cx.build_model(|cx| {
1420            let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), saved_conversation.text);
1421            for message in saved_conversation.messages {
1422                message_anchors.push(MessageAnchor {
1423                    id: message.id,
1424                    start: buffer.anchor_before(message.start),
1425                });
1426                next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
1427            }
1428            buffer.set_language_registry(language_registry);
1429            cx.spawn(|buffer, mut cx| async move {
1430                let markdown = markdown.await?;
1431                buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
1432                    buffer.set_language(Some(markdown), cx)
1433                })?;
1434                anyhow::Ok(())
1435            })
1436            .detach_and_log_err(cx);
1437            buffer
1438        });
1439
1440        let mut this = Self {
1441            id,
1442            message_anchors,
1443            messages_metadata: saved_conversation.message_metadata,
1444            next_message_id,
1445            summary: Some(Summary {
1446                text: saved_conversation.summary,
1447                done: true,
1448            }),
1449            pending_summary: Task::ready(None),
1450            completion_count: Default::default(),
1451            pending_completions: Default::default(),
1452            token_count: None,
1453            max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
1454            pending_token_count: Task::ready(None),
1455            model,
1456            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1457            pending_save: Task::ready(Ok(())),
1458            path: Some(path),
1459            buffer,
1460            completion_provider,
1461        };
1462        this.count_remaining_tokens(cx);
1463        this
1464    }
1465
1466    fn handle_buffer_event(
1467        &mut self,
1468        _: Model<Buffer>,
1469        event: &language::Event,
1470        cx: &mut ModelContext<Self>,
1471    ) {
1472        match event {
1473            language::Event::Edited => {
1474                self.count_remaining_tokens(cx);
1475                cx.emit(ConversationEvent::MessagesEdited);
1476            }
1477            _ => {}
1478        }
1479    }
1480
1481    fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
1482        let messages = self
1483            .messages(cx)
1484            .into_iter()
1485            .filter_map(|message| {
1486                Some(tiktoken_rs::ChatCompletionRequestMessage {
1487                    role: match message.role {
1488                        Role::User => "user".into(),
1489                        Role::Assistant => "assistant".into(),
1490                        Role::System => "system".into(),
1491                    },
1492                    content: Some(
1493                        self.buffer
1494                            .read(cx)
1495                            .text_for_range(message.offset_range)
1496                            .collect(),
1497                    ),
1498                    name: None,
1499                    function_call: None,
1500                })
1501            })
1502            .collect::<Vec<_>>();
1503        let model = self.model.clone();
1504        self.pending_token_count = cx.spawn(|this, mut cx| {
1505            async move {
1506                cx.background_executor()
1507                    .timer(Duration::from_millis(200))
1508                    .await;
1509                let token_count = cx
1510                    .background_executor()
1511                    .spawn(async move {
1512                        tiktoken_rs::num_tokens_from_messages(&model.full_name(), &messages)
1513                    })
1514                    .await?;
1515
1516                this.update(&mut cx, |this, cx| {
1517                    this.max_token_count =
1518                        tiktoken_rs::model::get_context_size(&this.model.full_name());
1519                    this.token_count = Some(token_count);
1520                    cx.notify()
1521                })?;
1522                anyhow::Ok(())
1523            }
1524            .log_err()
1525        });
1526    }
1527
1528    fn remaining_tokens(&self) -> Option<isize> {
1529        Some(self.max_token_count as isize - self.token_count? as isize)
1530    }
1531
1532    fn set_model(&mut self, model: OpenAIModel, cx: &mut ModelContext<Self>) {
1533        self.model = model;
1534        self.count_remaining_tokens(cx);
1535        cx.notify();
1536    }
1537
1538    fn assist(
1539        &mut self,
1540        selected_messages: HashSet<MessageId>,
1541        cx: &mut ModelContext<Self>,
1542    ) -> Vec<MessageAnchor> {
1543        let mut user_messages = Vec::new();
1544
1545        let last_message_id = if let Some(last_message_id) =
1546            self.message_anchors.iter().rev().find_map(|message| {
1547                message
1548                    .start
1549                    .is_valid(self.buffer.read(cx))
1550                    .then_some(message.id)
1551            }) {
1552            last_message_id
1553        } else {
1554            return Default::default();
1555        };
1556
1557        let mut should_assist = false;
1558        for selected_message_id in selected_messages {
1559            let selected_message_role =
1560                if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
1561                    metadata.role
1562                } else {
1563                    continue;
1564                };
1565
1566            if selected_message_role == Role::Assistant {
1567                if let Some(user_message) = self.insert_message_after(
1568                    selected_message_id,
1569                    Role::User,
1570                    MessageStatus::Done,
1571                    cx,
1572                ) {
1573                    user_messages.push(user_message);
1574                }
1575            } else {
1576                should_assist = true;
1577            }
1578        }
1579
1580        if should_assist {
1581            if !self.completion_provider.has_credentials() {
1582                return Default::default();
1583            }
1584
1585            let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
1586                model: self.model.full_name().to_string(),
1587                messages: self
1588                    .messages(cx)
1589                    .filter(|message| matches!(message.status, MessageStatus::Done))
1590                    .map(|message| message.to_open_ai_message(self.buffer.read(cx)))
1591                    .collect(),
1592                stream: true,
1593                stop: vec![],
1594                temperature: 1.0,
1595            });
1596
1597            let stream = self.completion_provider.complete(request);
1598            let assistant_message = self
1599                .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
1600                .unwrap();
1601
1602            // Queue up the user's next reply.
1603            let user_message = self
1604                .insert_message_after(assistant_message.id, Role::User, MessageStatus::Done, cx)
1605                .unwrap();
1606            user_messages.push(user_message);
1607
1608            let task = cx.spawn({
1609                |this, mut cx| async move {
1610                    let assistant_message_id = assistant_message.id;
1611                    let stream_completion = async {
1612                        let mut messages = stream.await?;
1613
1614                        while let Some(message) = messages.next().await {
1615                            let text = message?;
1616
1617                            this.update(&mut cx, |this, cx| {
1618                                let message_ix = this
1619                                    .message_anchors
1620                                    .iter()
1621                                    .position(|message| message.id == assistant_message_id)?;
1622                                this.buffer.update(cx, |buffer, cx| {
1623                                    let offset = this.message_anchors[message_ix + 1..]
1624                                        .iter()
1625                                        .find(|message| message.start.is_valid(buffer))
1626                                        .map_or(buffer.len(), |message| {
1627                                            message.start.to_offset(buffer).saturating_sub(1)
1628                                        });
1629                                    buffer.edit([(offset..offset, text)], None, cx);
1630                                });
1631                                cx.emit(ConversationEvent::StreamedCompletion);
1632
1633                                Some(())
1634                            })?;
1635                            smol::future::yield_now().await;
1636                        }
1637
1638                        this.update(&mut cx, |this, cx| {
1639                            this.pending_completions
1640                                .retain(|completion| completion.id != this.completion_count);
1641                            this.summarize(cx);
1642                        })?;
1643
1644                        anyhow::Ok(())
1645                    };
1646
1647                    let result = stream_completion.await;
1648
1649                    this.update(&mut cx, |this, cx| {
1650                        if let Some(metadata) =
1651                            this.messages_metadata.get_mut(&assistant_message.id)
1652                        {
1653                            match result {
1654                                Ok(_) => {
1655                                    metadata.status = MessageStatus::Done;
1656                                }
1657                                Err(error) => {
1658                                    metadata.status = MessageStatus::Error(SharedString::from(
1659                                        error.to_string().trim().to_string(),
1660                                    ));
1661                                }
1662                            }
1663                            cx.notify();
1664                        }
1665                    })
1666                    .ok();
1667                }
1668            });
1669
1670            self.pending_completions.push(PendingCompletion {
1671                id: post_inc(&mut self.completion_count),
1672                _task: task,
1673            });
1674        }
1675
1676        user_messages
1677    }
1678
1679    fn cancel_last_assist(&mut self) -> bool {
1680        self.pending_completions.pop().is_some()
1681    }
1682
1683    fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
1684        for id in ids {
1685            if let Some(metadata) = self.messages_metadata.get_mut(&id) {
1686                metadata.role.cycle();
1687                cx.emit(ConversationEvent::MessagesEdited);
1688                cx.notify();
1689            }
1690        }
1691    }
1692
1693    fn insert_message_after(
1694        &mut self,
1695        message_id: MessageId,
1696        role: Role,
1697        status: MessageStatus,
1698        cx: &mut ModelContext<Self>,
1699    ) -> Option<MessageAnchor> {
1700        if let Some(prev_message_ix) = self
1701            .message_anchors
1702            .iter()
1703            .position(|message| message.id == message_id)
1704        {
1705            // Find the next valid message after the one we were given.
1706            let mut next_message_ix = prev_message_ix + 1;
1707            while let Some(next_message) = self.message_anchors.get(next_message_ix) {
1708                if next_message.start.is_valid(self.buffer.read(cx)) {
1709                    break;
1710                }
1711                next_message_ix += 1;
1712            }
1713
1714            let start = self.buffer.update(cx, |buffer, cx| {
1715                let offset = self
1716                    .message_anchors
1717                    .get(next_message_ix)
1718                    .map_or(buffer.len(), |message| message.start.to_offset(buffer) - 1);
1719                buffer.edit([(offset..offset, "\n")], None, cx);
1720                buffer.anchor_before(offset + 1)
1721            });
1722            let message = MessageAnchor {
1723                id: MessageId(post_inc(&mut self.next_message_id.0)),
1724                start,
1725            };
1726            self.message_anchors
1727                .insert(next_message_ix, message.clone());
1728            self.messages_metadata.insert(
1729                message.id,
1730                MessageMetadata {
1731                    role,
1732                    sent_at: Local::now(),
1733                    status,
1734                },
1735            );
1736            cx.emit(ConversationEvent::MessagesEdited);
1737            Some(message)
1738        } else {
1739            None
1740        }
1741    }
1742
1743    fn split_message(
1744        &mut self,
1745        range: Range<usize>,
1746        cx: &mut ModelContext<Self>,
1747    ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
1748        let start_message = self.message_for_offset(range.start, cx);
1749        let end_message = self.message_for_offset(range.end, cx);
1750        if let Some((start_message, end_message)) = start_message.zip(end_message) {
1751            // Prevent splitting when range spans multiple messages.
1752            if start_message.id != end_message.id {
1753                return (None, None);
1754            }
1755
1756            let message = start_message;
1757            let role = message.role;
1758            let mut edited_buffer = false;
1759
1760            let mut suffix_start = None;
1761            if range.start > message.offset_range.start && range.end < message.offset_range.end - 1
1762            {
1763                if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
1764                    suffix_start = Some(range.end + 1);
1765                } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
1766                    suffix_start = Some(range.end);
1767                }
1768            }
1769
1770            let suffix = if let Some(suffix_start) = suffix_start {
1771                MessageAnchor {
1772                    id: MessageId(post_inc(&mut self.next_message_id.0)),
1773                    start: self.buffer.read(cx).anchor_before(suffix_start),
1774                }
1775            } else {
1776                self.buffer.update(cx, |buffer, cx| {
1777                    buffer.edit([(range.end..range.end, "\n")], None, cx);
1778                });
1779                edited_buffer = true;
1780                MessageAnchor {
1781                    id: MessageId(post_inc(&mut self.next_message_id.0)),
1782                    start: self.buffer.read(cx).anchor_before(range.end + 1),
1783                }
1784            };
1785
1786            self.message_anchors
1787                .insert(message.index_range.end + 1, suffix.clone());
1788            self.messages_metadata.insert(
1789                suffix.id,
1790                MessageMetadata {
1791                    role,
1792                    sent_at: Local::now(),
1793                    status: MessageStatus::Done,
1794                },
1795            );
1796
1797            let new_messages =
1798                if range.start == range.end || range.start == message.offset_range.start {
1799                    (None, Some(suffix))
1800                } else {
1801                    let mut prefix_end = None;
1802                    if range.start > message.offset_range.start
1803                        && range.end < message.offset_range.end - 1
1804                    {
1805                        if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
1806                            prefix_end = Some(range.start + 1);
1807                        } else if self.buffer.read(cx).reversed_chars_at(range.start).next()
1808                            == Some('\n')
1809                        {
1810                            prefix_end = Some(range.start);
1811                        }
1812                    }
1813
1814                    let selection = if let Some(prefix_end) = prefix_end {
1815                        cx.emit(ConversationEvent::MessagesEdited);
1816                        MessageAnchor {
1817                            id: MessageId(post_inc(&mut self.next_message_id.0)),
1818                            start: self.buffer.read(cx).anchor_before(prefix_end),
1819                        }
1820                    } else {
1821                        self.buffer.update(cx, |buffer, cx| {
1822                            buffer.edit([(range.start..range.start, "\n")], None, cx)
1823                        });
1824                        edited_buffer = true;
1825                        MessageAnchor {
1826                            id: MessageId(post_inc(&mut self.next_message_id.0)),
1827                            start: self.buffer.read(cx).anchor_before(range.end + 1),
1828                        }
1829                    };
1830
1831                    self.message_anchors
1832                        .insert(message.index_range.end + 1, selection.clone());
1833                    self.messages_metadata.insert(
1834                        selection.id,
1835                        MessageMetadata {
1836                            role,
1837                            sent_at: Local::now(),
1838                            status: MessageStatus::Done,
1839                        },
1840                    );
1841                    (Some(selection), Some(suffix))
1842                };
1843
1844            if !edited_buffer {
1845                cx.emit(ConversationEvent::MessagesEdited);
1846            }
1847            new_messages
1848        } else {
1849            (None, None)
1850        }
1851    }
1852
1853    fn summarize(&mut self, cx: &mut ModelContext<Self>) {
1854        if self.message_anchors.len() >= 2 && self.summary.is_none() {
1855            if !self.completion_provider.has_credentials() {
1856                return;
1857            }
1858
1859            let messages = self
1860                .messages(cx)
1861                .take(2)
1862                .map(|message| message.to_open_ai_message(self.buffer.read(cx)))
1863                .chain(Some(RequestMessage {
1864                    role: Role::User,
1865                    content: "Summarize the conversation into a short title without punctuation"
1866                        .into(),
1867                }));
1868            let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
1869                model: self.model.full_name().to_string(),
1870                messages: messages.collect(),
1871                stream: true,
1872                stop: vec![],
1873                temperature: 1.0,
1874            });
1875
1876            let stream = self.completion_provider.complete(request);
1877            self.pending_summary = cx.spawn(|this, mut cx| {
1878                async move {
1879                    let mut messages = stream.await?;
1880
1881                    while let Some(message) = messages.next().await {
1882                        let text = message?;
1883                        this.update(&mut cx, |this, cx| {
1884                            this.summary
1885                                .get_or_insert(Default::default())
1886                                .text
1887                                .push_str(&text);
1888                            cx.emit(ConversationEvent::SummaryChanged);
1889                        })?;
1890                    }
1891
1892                    this.update(&mut cx, |this, cx| {
1893                        if let Some(summary) = this.summary.as_mut() {
1894                            summary.done = true;
1895                            cx.emit(ConversationEvent::SummaryChanged);
1896                        }
1897                    })?;
1898
1899                    anyhow::Ok(())
1900                }
1901                .log_err()
1902            });
1903        }
1904    }
1905
1906    fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
1907        self.messages_for_offsets([offset], cx).pop()
1908    }
1909
1910    fn messages_for_offsets(
1911        &self,
1912        offsets: impl IntoIterator<Item = usize>,
1913        cx: &AppContext,
1914    ) -> Vec<Message> {
1915        let mut result = Vec::new();
1916
1917        let mut messages = self.messages(cx).peekable();
1918        let mut offsets = offsets.into_iter().peekable();
1919        let mut current_message = messages.next();
1920        while let Some(offset) = offsets.next() {
1921            // Locate the message that contains the offset.
1922            while current_message.as_ref().map_or(false, |message| {
1923                !message.offset_range.contains(&offset) && messages.peek().is_some()
1924            }) {
1925                current_message = messages.next();
1926            }
1927            let Some(message) = current_message.as_ref() else {
1928                break;
1929            };
1930
1931            // Skip offsets that are in the same message.
1932            while offsets.peek().map_or(false, |offset| {
1933                message.offset_range.contains(offset) || messages.peek().is_none()
1934            }) {
1935                offsets.next();
1936            }
1937
1938            result.push(message.clone());
1939        }
1940        result
1941    }
1942
1943    fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
1944        let buffer = self.buffer.read(cx);
1945        let mut message_anchors = self.message_anchors.iter().enumerate().peekable();
1946        iter::from_fn(move || {
1947            while let Some((start_ix, message_anchor)) = message_anchors.next() {
1948                let metadata = self.messages_metadata.get(&message_anchor.id)?;
1949                let message_start = message_anchor.start.to_offset(buffer);
1950                let mut message_end = None;
1951                let mut end_ix = start_ix;
1952                while let Some((_, next_message)) = message_anchors.peek() {
1953                    if next_message.start.is_valid(buffer) {
1954                        message_end = Some(next_message.start);
1955                        break;
1956                    } else {
1957                        end_ix += 1;
1958                        message_anchors.next();
1959                    }
1960                }
1961                let message_end = message_end
1962                    .unwrap_or(language::Anchor::MAX)
1963                    .to_offset(buffer);
1964                return Some(Message {
1965                    index_range: start_ix..end_ix,
1966                    offset_range: message_start..message_end,
1967                    id: message_anchor.id,
1968                    anchor: message_anchor.start,
1969                    role: metadata.role,
1970                    sent_at: metadata.sent_at,
1971                    status: metadata.status.clone(),
1972                });
1973            }
1974            None
1975        })
1976    }
1977
1978    fn save(
1979        &mut self,
1980        debounce: Option<Duration>,
1981        fs: Arc<dyn Fs>,
1982        cx: &mut ModelContext<Conversation>,
1983    ) {
1984        self.pending_save = cx.spawn(|this, mut cx| async move {
1985            if let Some(debounce) = debounce {
1986                cx.background_executor().timer(debounce).await;
1987            }
1988
1989            let (old_path, summary) = this.read_with(&cx, |this, _| {
1990                let path = this.path.clone();
1991                let summary = if let Some(summary) = this.summary.as_ref() {
1992                    if summary.done {
1993                        Some(summary.text.clone())
1994                    } else {
1995                        None
1996                    }
1997                } else {
1998                    None
1999                };
2000                (path, summary)
2001            })?;
2002
2003            if let Some(summary) = summary {
2004                let conversation = this.read_with(&cx, |this, cx| this.serialize(cx))?;
2005                let path = if let Some(old_path) = old_path {
2006                    old_path
2007                } else {
2008                    let mut discriminant = 1;
2009                    let mut new_path;
2010                    loop {
2011                        new_path = CONVERSATIONS_DIR.join(&format!(
2012                            "{} - {}.zed.json",
2013                            summary.trim(),
2014                            discriminant
2015                        ));
2016                        if fs.is_file(&new_path).await {
2017                            discriminant += 1;
2018                        } else {
2019                            break;
2020                        }
2021                    }
2022                    new_path
2023                };
2024
2025                fs.create_dir(CONVERSATIONS_DIR.as_ref()).await?;
2026                fs.atomic_write(path.clone(), serde_json::to_string(&conversation).unwrap())
2027                    .await?;
2028                this.update(&mut cx, |this, _| this.path = Some(path))?;
2029            }
2030
2031            Ok(())
2032        });
2033    }
2034}
2035
2036struct PendingCompletion {
2037    id: usize,
2038    _task: Task<()>,
2039}
2040
2041enum ConversationEditorEvent {
2042    TabContentChanged,
2043}
2044
2045#[derive(Copy, Clone, Debug, PartialEq)]
2046struct ScrollPosition {
2047    offset_before_cursor: gpui::Point<f32>,
2048    cursor: Anchor,
2049}
2050
2051struct ConversationEditor {
2052    conversation: Model<Conversation>,
2053    fs: Arc<dyn Fs>,
2054    workspace: WeakView<Workspace>,
2055    editor: View<Editor>,
2056    blocks: HashSet<BlockId>,
2057    scroll_position: Option<ScrollPosition>,
2058    _subscriptions: Vec<Subscription>,
2059}
2060
2061impl ConversationEditor {
2062    fn new(
2063        completion_provider: Arc<dyn CompletionProvider>,
2064        language_registry: Arc<LanguageRegistry>,
2065        fs: Arc<dyn Fs>,
2066        workspace: WeakView<Workspace>,
2067        cx: &mut ViewContext<Self>,
2068    ) -> Self {
2069        let conversation =
2070            cx.build_model(|cx| Conversation::new(language_registry, cx, completion_provider));
2071        Self::for_conversation(conversation, fs, workspace, cx)
2072    }
2073
2074    fn for_conversation(
2075        conversation: Model<Conversation>,
2076        fs: Arc<dyn Fs>,
2077        workspace: WeakView<Workspace>,
2078        cx: &mut ViewContext<Self>,
2079    ) -> Self {
2080        let editor = cx.build_view(|cx| {
2081            let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx);
2082            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
2083            editor.set_show_gutter(false, cx);
2084            editor.set_show_wrap_guides(false, cx);
2085            editor
2086        });
2087
2088        let _subscriptions = vec![
2089            cx.observe(&conversation, |_, _, cx| cx.notify()),
2090            cx.subscribe(&conversation, Self::handle_conversation_event),
2091            cx.subscribe(&editor, Self::handle_editor_event),
2092        ];
2093
2094        let mut this = Self {
2095            conversation,
2096            editor,
2097            blocks: Default::default(),
2098            scroll_position: None,
2099            fs,
2100            workspace,
2101            _subscriptions,
2102        };
2103        this.update_message_headers(cx);
2104        this
2105    }
2106
2107    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
2108        report_assistant_event(
2109            self.workspace.clone(),
2110            self.conversation.read(cx).id.clone(),
2111            AssistantKind::Panel,
2112            cx,
2113        );
2114
2115        let cursors = self.cursors(cx);
2116
2117        let user_messages = self.conversation.update(cx, |conversation, cx| {
2118            let selected_messages = conversation
2119                .messages_for_offsets(cursors, cx)
2120                .into_iter()
2121                .map(|message| message.id)
2122                .collect();
2123            conversation.assist(selected_messages, cx)
2124        });
2125        let new_selections = user_messages
2126            .iter()
2127            .map(|message| {
2128                let cursor = message
2129                    .start
2130                    .to_offset(self.conversation.read(cx).buffer.read(cx));
2131                cursor..cursor
2132            })
2133            .collect::<Vec<_>>();
2134        if !new_selections.is_empty() {
2135            self.editor.update(cx, |editor, cx| {
2136                editor.change_selections(
2137                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
2138                    cx,
2139                    |selections| selections.select_ranges(new_selections),
2140                );
2141            });
2142            // Avoid scrolling to the new cursor position so the assistant's output is stable.
2143            cx.defer(|this, _| this.scroll_position = None);
2144        }
2145    }
2146
2147    fn cancel_last_assist(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
2148        if !self
2149            .conversation
2150            .update(cx, |conversation, _| conversation.cancel_last_assist())
2151        {
2152            cx.propagate();
2153        }
2154    }
2155
2156    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
2157        let cursors = self.cursors(cx);
2158        self.conversation.update(cx, |conversation, cx| {
2159            let messages = conversation
2160                .messages_for_offsets(cursors, cx)
2161                .into_iter()
2162                .map(|message| message.id)
2163                .collect();
2164            conversation.cycle_message_roles(messages, cx)
2165        });
2166    }
2167
2168    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2169        let selections = self.editor.read(cx).selections.all::<usize>(cx);
2170        selections
2171            .into_iter()
2172            .map(|selection| selection.head())
2173            .collect()
2174    }
2175
2176    fn handle_conversation_event(
2177        &mut self,
2178        _: Model<Conversation>,
2179        event: &ConversationEvent,
2180        cx: &mut ViewContext<Self>,
2181    ) {
2182        match event {
2183            ConversationEvent::MessagesEdited => {
2184                self.update_message_headers(cx);
2185                self.conversation.update(cx, |conversation, cx| {
2186                    conversation.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2187                });
2188            }
2189            ConversationEvent::SummaryChanged => {
2190                cx.emit(ConversationEditorEvent::TabContentChanged);
2191                self.conversation.update(cx, |conversation, cx| {
2192                    conversation.save(None, self.fs.clone(), cx);
2193                });
2194            }
2195            ConversationEvent::StreamedCompletion => {
2196                self.editor.update(cx, |editor, cx| {
2197                    if let Some(scroll_position) = self.scroll_position {
2198                        let snapshot = editor.snapshot(cx);
2199                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2200                        let scroll_top =
2201                            cursor_point.row() as f32 - scroll_position.offset_before_cursor.y;
2202                        editor.set_scroll_position(
2203                            point(scroll_position.offset_before_cursor.x, scroll_top),
2204                            cx,
2205                        );
2206                    }
2207                });
2208            }
2209        }
2210    }
2211
2212    fn handle_editor_event(
2213        &mut self,
2214        _: View<Editor>,
2215        event: &EditorEvent,
2216        cx: &mut ViewContext<Self>,
2217    ) {
2218        match event {
2219            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2220                let cursor_scroll_position = self.cursor_scroll_position(cx);
2221                if *autoscroll {
2222                    self.scroll_position = cursor_scroll_position;
2223                } else if self.scroll_position != cursor_scroll_position {
2224                    self.scroll_position = None;
2225                }
2226            }
2227            EditorEvent::SelectionsChanged { .. } => {
2228                self.scroll_position = self.cursor_scroll_position(cx);
2229            }
2230            _ => {}
2231        }
2232    }
2233
2234    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2235        self.editor.update(cx, |editor, cx| {
2236            let snapshot = editor.snapshot(cx);
2237            let cursor = editor.selections.newest_anchor().head();
2238            let cursor_row = cursor.to_display_point(&snapshot.display_snapshot).row() as f32;
2239            let scroll_position = editor
2240                .scroll_manager
2241                .anchor()
2242                .scroll_position(&snapshot.display_snapshot);
2243
2244            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2245            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2246                Some(ScrollPosition {
2247                    cursor,
2248                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2249                })
2250            } else {
2251                None
2252            }
2253        })
2254    }
2255
2256    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2257        self.editor.update(cx, |editor, cx| {
2258            let buffer = editor.buffer().read(cx).snapshot(cx);
2259            let excerpt_id = *buffer.as_singleton().unwrap().0;
2260            let old_blocks = std::mem::take(&mut self.blocks);
2261            let new_blocks = self
2262                .conversation
2263                .read(cx)
2264                .messages(cx)
2265                .map(|message| BlockProperties {
2266                    position: buffer.anchor_in_excerpt(excerpt_id, message.anchor),
2267                    height: 2,
2268                    style: BlockStyle::Sticky,
2269                    render: Arc::new({
2270                        let conversation = self.conversation.clone();
2271                        move |_cx| {
2272                            let message_id = message.id;
2273                            let sender = ButtonLike::new("role")
2274                                .child(match message.role {
2275                                    Role::User => Label::new("You").color(Color::Default),
2276                                    Role::Assistant => {
2277                                        Label::new("Assistant").color(Color::Modified)
2278                                    }
2279                                    Role::System => Label::new("System").color(Color::Warning),
2280                                })
2281                                .on_click({
2282                                    let conversation = conversation.clone();
2283                                    move |_, cx| {
2284                                        conversation.update(cx, |conversation, cx| {
2285                                            conversation.cycle_message_roles(
2286                                                HashSet::from_iter(Some(message_id)),
2287                                                cx,
2288                                            )
2289                                        })
2290                                    }
2291                                });
2292
2293                            h_stack()
2294                                .id(("message_header", message_id.0))
2295                                .border()
2296                                .border_color(gpui::red())
2297                                .child(sender)
2298                                .child(Label::new(message.sent_at.format("%I:%M%P").to_string()))
2299                                .children(
2300                                    if let MessageStatus::Error(error) = message.status.clone() {
2301                                        Some(
2302                                            div()
2303                                                .id("error")
2304                                                .tooltip(move |cx| Tooltip::text(error.clone(), cx))
2305                                                .child(IconElement::new(Icon::XCircle)),
2306                                        )
2307                                    } else {
2308                                        None
2309                                    },
2310                                )
2311                                .into_any_element()
2312                        }
2313                    }),
2314                    disposition: BlockDisposition::Above,
2315                })
2316                .collect::<Vec<_>>();
2317
2318            editor.remove_blocks(old_blocks, None, cx);
2319            let ids = editor.insert_blocks(new_blocks, None, cx);
2320            self.blocks = HashSet::from_iter(ids);
2321        });
2322    }
2323
2324    fn quote_selection(
2325        workspace: &mut Workspace,
2326        _: &QuoteSelection,
2327        cx: &mut ViewContext<Workspace>,
2328    ) {
2329        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2330            return;
2331        };
2332        let Some(editor) = workspace
2333            .active_item(cx)
2334            .and_then(|item| item.act_as::<Editor>(cx))
2335        else {
2336            return;
2337        };
2338
2339        let editor = editor.read(cx);
2340        let range = editor.selections.newest::<usize>(cx).range();
2341        let buffer = editor.buffer().read(cx).snapshot(cx);
2342        let start_language = buffer.language_at(range.start);
2343        let end_language = buffer.language_at(range.end);
2344        let language_name = if start_language == end_language {
2345            start_language.map(|language| language.name())
2346        } else {
2347            None
2348        };
2349        let language_name = language_name.as_deref().unwrap_or("").to_lowercase();
2350
2351        let selected_text = buffer.text_for_range(range).collect::<String>();
2352        let text = if selected_text.is_empty() {
2353            None
2354        } else {
2355            Some(if language_name == "markdown" {
2356                selected_text
2357                    .lines()
2358                    .map(|line| format!("> {}", line))
2359                    .collect::<Vec<_>>()
2360                    .join("\n")
2361            } else {
2362                format!("```{language_name}\n{selected_text}\n```")
2363            })
2364        };
2365
2366        // Activate the panel
2367        if !panel.focus_handle(cx).contains_focused(cx) {
2368            workspace.toggle_panel_focus::<AssistantPanel>(cx);
2369        }
2370
2371        if let Some(text) = text {
2372            panel.update(cx, |panel, cx| {
2373                let conversation = panel
2374                    .active_editor()
2375                    .cloned()
2376                    .unwrap_or_else(|| panel.new_conversation(cx));
2377                conversation.update(cx, |conversation, cx| {
2378                    conversation
2379                        .editor
2380                        .update(cx, |editor, cx| editor.insert(&text, cx))
2381                });
2382            });
2383        }
2384    }
2385
2386    fn copy(&mut self, _: &editor::Copy, cx: &mut ViewContext<Self>) {
2387        let editor = self.editor.read(cx);
2388        let conversation = self.conversation.read(cx);
2389        if editor.selections.count() == 1 {
2390            let selection = editor.selections.newest::<usize>(cx);
2391            let mut copied_text = String::new();
2392            let mut spanned_messages = 0;
2393            for message in conversation.messages(cx) {
2394                if message.offset_range.start >= selection.range().end {
2395                    break;
2396                } else if message.offset_range.end >= selection.range().start {
2397                    let range = cmp::max(message.offset_range.start, selection.range().start)
2398                        ..cmp::min(message.offset_range.end, selection.range().end);
2399                    if !range.is_empty() {
2400                        spanned_messages += 1;
2401                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
2402                        for chunk in conversation.buffer.read(cx).text_for_range(range) {
2403                            copied_text.push_str(&chunk);
2404                        }
2405                        copied_text.push('\n');
2406                    }
2407                }
2408            }
2409
2410            if spanned_messages > 1 {
2411                cx.write_to_clipboard(ClipboardItem::new(copied_text));
2412                return;
2413            }
2414        }
2415
2416        cx.propagate();
2417    }
2418
2419    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
2420        self.conversation.update(cx, |conversation, cx| {
2421            let selections = self.editor.read(cx).selections.disjoint_anchors();
2422            for selection in selections.into_iter() {
2423                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2424                let range = selection
2425                    .map(|endpoint| endpoint.to_offset(&buffer))
2426                    .range();
2427                conversation.split_message(range, cx);
2428            }
2429        });
2430    }
2431
2432    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
2433        self.conversation.update(cx, |conversation, cx| {
2434            conversation.save(None, self.fs.clone(), cx)
2435        });
2436    }
2437
2438    fn cycle_model(&mut self, cx: &mut ViewContext<Self>) {
2439        self.conversation.update(cx, |conversation, cx| {
2440            let new_model = conversation.model.cycle();
2441            conversation.set_model(new_model, cx);
2442        });
2443    }
2444
2445    fn title(&self, cx: &AppContext) -> String {
2446        self.conversation
2447            .read(cx)
2448            .summary
2449            .as_ref()
2450            .map(|summary| summary.text.clone())
2451            .unwrap_or_else(|| "New Conversation".into())
2452    }
2453
2454    fn render_current_model(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2455        Button::new(
2456            "current_model",
2457            self.conversation.read(cx).model.short_name(),
2458        )
2459        .tooltip(move |cx| Tooltip::text("Change Model", cx))
2460        .on_click(cx.listener(|this, _, cx| this.cycle_model(cx)))
2461    }
2462
2463    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
2464        let remaining_tokens = self.conversation.read(cx).remaining_tokens()?;
2465        let remaining_tokens_color = if remaining_tokens <= 0 {
2466            Color::Error
2467        } else if remaining_tokens <= 500 {
2468            Color::Warning
2469        } else {
2470            Color::Default
2471        };
2472        Some(
2473            div()
2474                .border()
2475                .border_color(gpui::red())
2476                .child(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color)),
2477        )
2478    }
2479}
2480
2481impl EventEmitter<ConversationEditorEvent> for ConversationEditor {}
2482
2483impl Render for ConversationEditor {
2484    type Element = Div;
2485
2486    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
2487        div()
2488            .key_context("ConversationEditor")
2489            .size_full()
2490            .relative()
2491            .capture_action(cx.listener(ConversationEditor::cancel_last_assist))
2492            .capture_action(cx.listener(ConversationEditor::save))
2493            .capture_action(cx.listener(ConversationEditor::copy))
2494            .capture_action(cx.listener(ConversationEditor::cycle_message_role))
2495            .on_action(cx.listener(ConversationEditor::assist))
2496            .on_action(cx.listener(ConversationEditor::split))
2497            .child(self.editor.clone())
2498            .child(
2499                h_stack()
2500                    .absolute()
2501                    .gap_1()
2502                    .top_3()
2503                    .right_5()
2504                    .child(self.render_current_model(cx))
2505                    .children(self.render_remaining_tokens(cx)),
2506            )
2507    }
2508}
2509
2510impl FocusableView for ConversationEditor {
2511    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
2512        self.editor.focus_handle(cx)
2513    }
2514}
2515
2516#[derive(Clone, Debug)]
2517struct MessageAnchor {
2518    id: MessageId,
2519    start: language::Anchor,
2520}
2521
2522#[derive(Clone, Debug)]
2523pub struct Message {
2524    offset_range: Range<usize>,
2525    index_range: Range<usize>,
2526    id: MessageId,
2527    anchor: language::Anchor,
2528    role: Role,
2529    sent_at: DateTime<Local>,
2530    status: MessageStatus,
2531}
2532
2533impl Message {
2534    fn to_open_ai_message(&self, buffer: &Buffer) -> RequestMessage {
2535        let content = buffer
2536            .text_for_range(self.offset_range.clone())
2537            .collect::<String>();
2538        RequestMessage {
2539            role: self.role,
2540            content: content.trim_end().into(),
2541        }
2542    }
2543}
2544
2545enum InlineAssistantEvent {
2546    Confirmed {
2547        prompt: String,
2548        include_conversation: bool,
2549        retrieve_context: bool,
2550    },
2551    Canceled,
2552    Dismissed,
2553    IncludeConversationToggled {
2554        include_conversation: bool,
2555    },
2556    RetrieveContextToggled {
2557        retrieve_context: bool,
2558    },
2559}
2560
2561struct InlineAssistant {
2562    id: usize,
2563    prompt_editor: View<Editor>,
2564    workspace: WeakView<Workspace>,
2565    confirmed: bool,
2566    include_conversation: bool,
2567    measurements: Rc<Cell<BlockMeasurements>>,
2568    prompt_history: VecDeque<String>,
2569    prompt_history_ix: Option<usize>,
2570    pending_prompt: String,
2571    codegen: Model<Codegen>,
2572    _subscriptions: Vec<Subscription>,
2573    retrieve_context: bool,
2574    semantic_index: Option<Model<SemanticIndex>>,
2575    semantic_permissioned: Option<bool>,
2576    project: WeakModel<Project>,
2577    maintain_rate_limit: Option<Task<()>>,
2578}
2579
2580impl EventEmitter<InlineAssistantEvent> for InlineAssistant {}
2581
2582impl Render for InlineAssistant {
2583    type Element = Div;
2584
2585    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
2586        let measurements = self.measurements.get();
2587        h_stack()
2588            .w_full()
2589            .py_2()
2590            .border_y_1()
2591            .border_color(cx.theme().colors().border)
2592            .on_action(cx.listener(Self::confirm))
2593            .on_action(cx.listener(Self::cancel))
2594            .on_action(cx.listener(Self::toggle_include_conversation))
2595            .on_action(cx.listener(Self::toggle_retrieve_context))
2596            .on_action(cx.listener(Self::move_up))
2597            .on_action(cx.listener(Self::move_down))
2598            .child(
2599                h_stack()
2600                    .justify_center()
2601                    .w(measurements.gutter_width)
2602                    .child(
2603                        IconButton::new("include_conversation", Icon::Ai)
2604                            .on_click(cx.listener(|this, _, cx| {
2605                                this.toggle_include_conversation(&ToggleIncludeConversation, cx)
2606                            }))
2607                            .selected(self.include_conversation)
2608                            .tooltip(|cx| {
2609                                Tooltip::for_action(
2610                                    "Include Conversation",
2611                                    &ToggleIncludeConversation,
2612                                    cx,
2613                                )
2614                            }),
2615                    )
2616                    .children(if SemanticIndex::enabled(cx) {
2617                        Some(
2618                            IconButton::new("retrieve_context", Icon::MagnifyingGlass)
2619                                .on_click(cx.listener(|this, _, cx| {
2620                                    this.toggle_retrieve_context(&ToggleRetrieveContext, cx)
2621                                }))
2622                                .selected(self.retrieve_context)
2623                                .tooltip(|cx| {
2624                                    Tooltip::for_action(
2625                                        "Retrieve Context",
2626                                        &ToggleRetrieveContext,
2627                                        cx,
2628                                    )
2629                                }),
2630                        )
2631                    } else {
2632                        None
2633                    })
2634                    .children(if let Some(error) = self.codegen.read(cx).error() {
2635                        let error_message = SharedString::from(error.to_string());
2636                        Some(
2637                            div()
2638                                .id("error")
2639                                .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
2640                                .child(IconElement::new(Icon::XCircle).color(Color::Error)),
2641                        )
2642                    } else {
2643                        None
2644                    }),
2645            )
2646            .child(
2647                h_stack()
2648                    .w_full()
2649                    .ml(measurements.anchor_x - measurements.gutter_width)
2650                    .child(self.render_prompt_editor(cx)),
2651            )
2652            .children(if self.retrieve_context {
2653                self.retrieve_context_status(cx)
2654            } else {
2655                None
2656            })
2657    }
2658}
2659
2660impl FocusableView for InlineAssistant {
2661    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
2662        self.prompt_editor.focus_handle(cx)
2663    }
2664}
2665
2666impl InlineAssistant {
2667    fn new(
2668        id: usize,
2669        measurements: Rc<Cell<BlockMeasurements>>,
2670        include_conversation: bool,
2671        prompt_history: VecDeque<String>,
2672        codegen: Model<Codegen>,
2673        workspace: WeakView<Workspace>,
2674        cx: &mut ViewContext<Self>,
2675        retrieve_context: bool,
2676        semantic_index: Option<Model<SemanticIndex>>,
2677        project: Model<Project>,
2678    ) -> Self {
2679        let prompt_editor = cx.build_view(|cx| {
2680            let mut editor = Editor::single_line(cx);
2681            let placeholder = match codegen.read(cx).kind() {
2682                CodegenKind::Transform { .. } => "Enter transformation prompt…",
2683                CodegenKind::Generate { .. } => "Enter generation prompt…",
2684            };
2685            editor.set_placeholder_text(placeholder, cx);
2686            editor
2687        });
2688        cx.focus_view(&prompt_editor);
2689
2690        let mut subscriptions = vec![
2691            cx.observe(&codegen, Self::handle_codegen_changed),
2692            cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
2693        ];
2694
2695        if let Some(semantic_index) = semantic_index.clone() {
2696            subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed));
2697        }
2698
2699        let assistant = Self {
2700            id,
2701            prompt_editor,
2702            workspace,
2703            confirmed: false,
2704            include_conversation,
2705            measurements,
2706            prompt_history,
2707            prompt_history_ix: None,
2708            pending_prompt: String::new(),
2709            codegen,
2710            _subscriptions: subscriptions,
2711            retrieve_context,
2712            semantic_permissioned: None,
2713            semantic_index,
2714            project: project.downgrade(),
2715            maintain_rate_limit: None,
2716        };
2717
2718        assistant.index_project(cx).log_err();
2719
2720        assistant
2721    }
2722
2723    fn semantic_permissioned(&self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
2724        if let Some(value) = self.semantic_permissioned {
2725            return Task::ready(Ok(value));
2726        }
2727
2728        let Some(project) = self.project.upgrade() else {
2729            return Task::ready(Err(anyhow!("project was dropped")));
2730        };
2731
2732        self.semantic_index
2733            .as_ref()
2734            .map(|semantic| {
2735                semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx))
2736            })
2737            .unwrap_or(Task::ready(Ok(false)))
2738    }
2739
2740    fn handle_prompt_editor_events(
2741        &mut self,
2742        _: View<Editor>,
2743        event: &EditorEvent,
2744        cx: &mut ViewContext<Self>,
2745    ) {
2746        if let EditorEvent::Edited = event {
2747            self.pending_prompt = self.prompt_editor.read(cx).text(cx);
2748            cx.notify();
2749        }
2750    }
2751
2752    fn semantic_index_changed(
2753        &mut self,
2754        semantic_index: Model<SemanticIndex>,
2755        cx: &mut ViewContext<Self>,
2756    ) {
2757        let Some(project) = self.project.upgrade() else {
2758            return;
2759        };
2760
2761        let status = semantic_index.read(cx).status(&project);
2762        match status {
2763            SemanticIndexStatus::Indexing {
2764                rate_limit_expiry: Some(_),
2765                ..
2766            } => {
2767                if self.maintain_rate_limit.is_none() {
2768                    self.maintain_rate_limit = Some(cx.spawn(|this, mut cx| async move {
2769                        loop {
2770                            cx.background_executor().timer(Duration::from_secs(1)).await;
2771                            this.update(&mut cx, |_, cx| cx.notify()).log_err();
2772                        }
2773                    }));
2774                }
2775                return;
2776            }
2777            _ => {
2778                self.maintain_rate_limit = None;
2779            }
2780        }
2781    }
2782
2783    fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
2784        let is_read_only = !self.codegen.read(cx).idle();
2785        self.prompt_editor.update(cx, |editor, _cx| {
2786            let was_read_only = editor.read_only();
2787            if was_read_only != is_read_only {
2788                if is_read_only {
2789                    editor.set_read_only(true);
2790                } else {
2791                    self.confirmed = false;
2792                    editor.set_read_only(false);
2793                }
2794            }
2795        });
2796        cx.notify();
2797    }
2798
2799    fn cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
2800        cx.emit(InlineAssistantEvent::Canceled);
2801    }
2802
2803    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
2804        if self.confirmed {
2805            cx.emit(InlineAssistantEvent::Dismissed);
2806        } else {
2807            report_assistant_event(self.workspace.clone(), None, AssistantKind::Inline, cx);
2808
2809            let prompt = self.prompt_editor.read(cx).text(cx);
2810            self.prompt_editor
2811                .update(cx, |editor, _cx| editor.set_read_only(true));
2812            cx.emit(InlineAssistantEvent::Confirmed {
2813                prompt,
2814                include_conversation: self.include_conversation,
2815                retrieve_context: self.retrieve_context,
2816            });
2817            self.confirmed = true;
2818            cx.notify();
2819        }
2820    }
2821
2822    fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext<Self>) {
2823        let semantic_permissioned = self.semantic_permissioned(cx);
2824
2825        let Some(project) = self.project.upgrade() else {
2826            return;
2827        };
2828
2829        let project_name = project
2830            .read(cx)
2831            .worktree_root_names(cx)
2832            .collect::<Vec<&str>>()
2833            .join("/");
2834        let is_plural = project_name.chars().filter(|letter| *letter == '/').count() > 0;
2835        let prompt_text = format!("Would you like to index the '{}' project{} for context retrieval? This requires sending code to the OpenAI API", project_name,
2836            if is_plural {
2837                "s"
2838            } else {""});
2839
2840        cx.spawn(|this, mut cx| async move {
2841            // If Necessary prompt user
2842            if !semantic_permissioned.await.unwrap_or(false) {
2843                let answer = this.update(&mut cx, |_, cx| {
2844                    cx.prompt(
2845                        PromptLevel::Info,
2846                        prompt_text.as_str(),
2847                        &["Continue", "Cancel"],
2848                    )
2849                })?;
2850
2851                if answer.await? == 0 {
2852                    this.update(&mut cx, |this, _| {
2853                        this.semantic_permissioned = Some(true);
2854                    })?;
2855                } else {
2856                    return anyhow::Ok(());
2857                }
2858            }
2859
2860            // If permissioned, update context appropriately
2861            this.update(&mut cx, |this, cx| {
2862                this.retrieve_context = !this.retrieve_context;
2863
2864                cx.emit(InlineAssistantEvent::RetrieveContextToggled {
2865                    retrieve_context: this.retrieve_context,
2866                });
2867
2868                if this.retrieve_context {
2869                    this.index_project(cx).log_err();
2870                }
2871
2872                cx.notify();
2873            })?;
2874
2875            anyhow::Ok(())
2876        })
2877        .detach_and_log_err(cx);
2878    }
2879
2880    fn index_project(&self, cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
2881        let Some(project) = self.project.upgrade() else {
2882            return Err(anyhow!("project was dropped!"));
2883        };
2884
2885        let semantic_permissioned = self.semantic_permissioned(cx);
2886        if let Some(semantic_index) = SemanticIndex::global(cx) {
2887            cx.spawn(|_, mut cx| async move {
2888                // This has to be updated to accomodate for semantic_permissions
2889                if semantic_permissioned.await.unwrap_or(false) {
2890                    semantic_index
2891                        .update(&mut cx, |index, cx| index.index_project(project, cx))?
2892                        .await
2893                } else {
2894                    Err(anyhow!("project is not permissioned for semantic indexing"))
2895                }
2896            })
2897            .detach_and_log_err(cx);
2898        }
2899
2900        anyhow::Ok(())
2901    }
2902
2903    fn retrieve_context_status(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
2904        let Some(project) = self.project.upgrade() else {
2905            return None;
2906        };
2907
2908        let semantic_index = SemanticIndex::global(cx)?;
2909        let status = semantic_index.update(cx, |index, _| index.status(&project));
2910        match status {
2911            SemanticIndexStatus::NotAuthenticated {} => Some(
2912                div()
2913                    .id("error")
2914                    .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx))
2915                    .child(IconElement::new(Icon::XCircle))
2916                    .into_any_element()
2917            ),
2918
2919            SemanticIndexStatus::NotIndexed {} => Some(
2920                div()
2921                    .id("error")
2922                    .tooltip(|cx| Tooltip::text("Not Indexed", cx))
2923                    .child(IconElement::new(Icon::XCircle))
2924                    .into_any_element()
2925            ),
2926
2927            SemanticIndexStatus::Indexing {
2928                remaining_files,
2929                rate_limit_expiry,
2930            } => {
2931                let mut status_text = if remaining_files == 0 {
2932                    "Indexing...".to_string()
2933                } else {
2934                    format!("Remaining files to index: {remaining_files}")
2935                };
2936
2937                if let Some(rate_limit_expiry) = rate_limit_expiry {
2938                    let remaining_seconds = rate_limit_expiry.duration_since(Instant::now());
2939                    if remaining_seconds > Duration::from_secs(0) && remaining_files > 0 {
2940                        write!(
2941                            status_text,
2942                            " (rate limit expires in {}s)",
2943                            remaining_seconds.as_secs()
2944                        )
2945                        .unwrap();
2946                    }
2947                }
2948
2949                let status_text = SharedString::from(status_text);
2950                Some(
2951                    div()
2952                        .id("update")
2953                        .tooltip(move |cx| Tooltip::text(status_text.clone(), cx))
2954                        .child(IconElement::new(Icon::Update).color(Color::Info))
2955                        .into_any_element()
2956                )
2957            }
2958
2959            SemanticIndexStatus::Indexed {} => Some(
2960                div()
2961                    .id("check")
2962                    .tooltip(|cx| Tooltip::text("Index up to date", cx))
2963                    .child(IconElement::new(Icon::Check).color(Color::Success))
2964                    .into_any_element()
2965            ),
2966        }
2967    }
2968
2969    fn toggle_include_conversation(
2970        &mut self,
2971        _: &ToggleIncludeConversation,
2972        cx: &mut ViewContext<Self>,
2973    ) {
2974        self.include_conversation = !self.include_conversation;
2975        cx.emit(InlineAssistantEvent::IncludeConversationToggled {
2976            include_conversation: self.include_conversation,
2977        });
2978        cx.notify();
2979    }
2980
2981    fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
2982        if let Some(ix) = self.prompt_history_ix {
2983            if ix > 0 {
2984                self.prompt_history_ix = Some(ix - 1);
2985                let prompt = self.prompt_history[ix - 1].clone();
2986                self.set_prompt(&prompt, cx);
2987            }
2988        } else if !self.prompt_history.is_empty() {
2989            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
2990            let prompt = self.prompt_history[self.prompt_history.len() - 1].clone();
2991            self.set_prompt(&prompt, cx);
2992        }
2993    }
2994
2995    fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
2996        if let Some(ix) = self.prompt_history_ix {
2997            if ix < self.prompt_history.len() - 1 {
2998                self.prompt_history_ix = Some(ix + 1);
2999                let prompt = self.prompt_history[ix + 1].clone();
3000                self.set_prompt(&prompt, cx);
3001            } else {
3002                self.prompt_history_ix = None;
3003                let pending_prompt = self.pending_prompt.clone();
3004                self.set_prompt(&pending_prompt, cx);
3005            }
3006        }
3007    }
3008
3009    fn set_prompt(&mut self, prompt: &str, cx: &mut ViewContext<Self>) {
3010        self.prompt_editor.update(cx, |editor, cx| {
3011            editor.buffer().update(cx, |buffer, cx| {
3012                let len = buffer.len(cx);
3013                buffer.edit([(0..len, prompt)], None, cx);
3014            });
3015        });
3016    }
3017
3018    fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3019        let settings = ThemeSettings::get_global(cx);
3020        let text_style = TextStyle {
3021            color: if self.prompt_editor.read(cx).read_only() {
3022                cx.theme().colors().text_disabled
3023            } else {
3024                cx.theme().colors().text
3025            },
3026            font_family: settings.ui_font.family.clone(),
3027            font_features: settings.ui_font.features,
3028            font_size: rems(0.875).into(),
3029            font_weight: FontWeight::NORMAL,
3030            font_style: FontStyle::Normal,
3031            line_height: relative(1.).into(),
3032            background_color: None,
3033            underline: None,
3034            white_space: WhiteSpace::Normal,
3035        };
3036        EditorElement::new(
3037            &self.prompt_editor,
3038            EditorStyle {
3039                background: cx.theme().colors().editor_background,
3040                local_player: cx.theme().players().local(),
3041                text: text_style,
3042                ..Default::default()
3043            },
3044        )
3045    }
3046}
3047
3048// This wouldn't need to exist if we could pass parameters when rendering child views.
3049#[derive(Copy, Clone, Default)]
3050struct BlockMeasurements {
3051    anchor_x: Pixels,
3052    gutter_width: Pixels,
3053}
3054
3055struct PendingInlineAssist {
3056    editor: WeakView<Editor>,
3057    inline_assistant: Option<(BlockId, View<InlineAssistant>)>,
3058    codegen: Model<Codegen>,
3059    _subscriptions: Vec<Subscription>,
3060    project: WeakModel<Project>,
3061}
3062
3063fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
3064    ranges.sort_unstable_by(|a, b| {
3065        a.start
3066            .cmp(&b.start, buffer)
3067            .then_with(|| b.end.cmp(&a.end, buffer))
3068    });
3069
3070    let mut ix = 0;
3071    while ix + 1 < ranges.len() {
3072        let b = ranges[ix + 1].clone();
3073        let a = &mut ranges[ix];
3074        if a.end.cmp(&b.start, buffer).is_gt() {
3075            if a.end.cmp(&b.end, buffer).is_lt() {
3076                a.end = b.end;
3077            }
3078            ranges.remove(ix + 1);
3079        } else {
3080            ix += 1;
3081        }
3082    }
3083}
3084
3085#[cfg(test)]
3086mod tests {
3087    use super::*;
3088    use crate::MessageId;
3089    use ai::test::FakeCompletionProvider;
3090    use gpui::AppContext;
3091
3092    #[gpui::test]
3093    fn test_inserting_and_removing_messages(cx: &mut AppContext) {
3094        let settings_store = SettingsStore::test(cx);
3095        cx.set_global(settings_store);
3096        init(cx);
3097        let registry = Arc::new(LanguageRegistry::test());
3098
3099        let completion_provider = Arc::new(FakeCompletionProvider::new());
3100        let conversation =
3101            cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
3102        let buffer = conversation.read(cx).buffer.clone();
3103
3104        let message_1 = conversation.read(cx).message_anchors[0].clone();
3105        assert_eq!(
3106            messages(&conversation, cx),
3107            vec![(message_1.id, Role::User, 0..0)]
3108        );
3109
3110        let message_2 = conversation.update(cx, |conversation, cx| {
3111            conversation
3112                .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
3113                .unwrap()
3114        });
3115        assert_eq!(
3116            messages(&conversation, cx),
3117            vec![
3118                (message_1.id, Role::User, 0..1),
3119                (message_2.id, Role::Assistant, 1..1)
3120            ]
3121        );
3122
3123        buffer.update(cx, |buffer, cx| {
3124            buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
3125        });
3126        assert_eq!(
3127            messages(&conversation, cx),
3128            vec![
3129                (message_1.id, Role::User, 0..2),
3130                (message_2.id, Role::Assistant, 2..3)
3131            ]
3132        );
3133
3134        let message_3 = conversation.update(cx, |conversation, cx| {
3135            conversation
3136                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3137                .unwrap()
3138        });
3139        assert_eq!(
3140            messages(&conversation, cx),
3141            vec![
3142                (message_1.id, Role::User, 0..2),
3143                (message_2.id, Role::Assistant, 2..4),
3144                (message_3.id, Role::User, 4..4)
3145            ]
3146        );
3147
3148        let message_4 = conversation.update(cx, |conversation, cx| {
3149            conversation
3150                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3151                .unwrap()
3152        });
3153        assert_eq!(
3154            messages(&conversation, cx),
3155            vec![
3156                (message_1.id, Role::User, 0..2),
3157                (message_2.id, Role::Assistant, 2..4),
3158                (message_4.id, Role::User, 4..5),
3159                (message_3.id, Role::User, 5..5),
3160            ]
3161        );
3162
3163        buffer.update(cx, |buffer, cx| {
3164            buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
3165        });
3166        assert_eq!(
3167            messages(&conversation, cx),
3168            vec![
3169                (message_1.id, Role::User, 0..2),
3170                (message_2.id, Role::Assistant, 2..4),
3171                (message_4.id, Role::User, 4..6),
3172                (message_3.id, Role::User, 6..7),
3173            ]
3174        );
3175
3176        // Deleting across message boundaries merges the messages.
3177        buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
3178        assert_eq!(
3179            messages(&conversation, cx),
3180            vec![
3181                (message_1.id, Role::User, 0..3),
3182                (message_3.id, Role::User, 3..4),
3183            ]
3184        );
3185
3186        // Undoing the deletion should also undo the merge.
3187        buffer.update(cx, |buffer, cx| buffer.undo(cx));
3188        assert_eq!(
3189            messages(&conversation, cx),
3190            vec![
3191                (message_1.id, Role::User, 0..2),
3192                (message_2.id, Role::Assistant, 2..4),
3193                (message_4.id, Role::User, 4..6),
3194                (message_3.id, Role::User, 6..7),
3195            ]
3196        );
3197
3198        // Redoing the deletion should also redo the merge.
3199        buffer.update(cx, |buffer, cx| buffer.redo(cx));
3200        assert_eq!(
3201            messages(&conversation, cx),
3202            vec![
3203                (message_1.id, Role::User, 0..3),
3204                (message_3.id, Role::User, 3..4),
3205            ]
3206        );
3207
3208        // Ensure we can still insert after a merged message.
3209        let message_5 = conversation.update(cx, |conversation, cx| {
3210            conversation
3211                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3212                .unwrap()
3213        });
3214        assert_eq!(
3215            messages(&conversation, cx),
3216            vec![
3217                (message_1.id, Role::User, 0..3),
3218                (message_5.id, Role::System, 3..4),
3219                (message_3.id, Role::User, 4..5)
3220            ]
3221        );
3222    }
3223
3224    #[gpui::test]
3225    fn test_message_splitting(cx: &mut AppContext) {
3226        let settings_store = SettingsStore::test(cx);
3227        cx.set_global(settings_store);
3228        init(cx);
3229        let registry = Arc::new(LanguageRegistry::test());
3230        let completion_provider = Arc::new(FakeCompletionProvider::new());
3231
3232        let conversation =
3233            cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
3234        let buffer = conversation.read(cx).buffer.clone();
3235
3236        let message_1 = conversation.read(cx).message_anchors[0].clone();
3237        assert_eq!(
3238            messages(&conversation, cx),
3239            vec![(message_1.id, Role::User, 0..0)]
3240        );
3241
3242        buffer.update(cx, |buffer, cx| {
3243            buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
3244        });
3245
3246        let (_, message_2) =
3247            conversation.update(cx, |conversation, cx| conversation.split_message(3..3, cx));
3248        let message_2 = message_2.unwrap();
3249
3250        // We recycle newlines in the middle of a split message
3251        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
3252        assert_eq!(
3253            messages(&conversation, cx),
3254            vec![
3255                (message_1.id, Role::User, 0..4),
3256                (message_2.id, Role::User, 4..16),
3257            ]
3258        );
3259
3260        let (_, message_3) =
3261            conversation.update(cx, |conversation, cx| conversation.split_message(3..3, cx));
3262        let message_3 = message_3.unwrap();
3263
3264        // We don't recycle newlines at the end of a split message
3265        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3266        assert_eq!(
3267            messages(&conversation, cx),
3268            vec![
3269                (message_1.id, Role::User, 0..4),
3270                (message_3.id, Role::User, 4..5),
3271                (message_2.id, Role::User, 5..17),
3272            ]
3273        );
3274
3275        let (_, message_4) =
3276            conversation.update(cx, |conversation, cx| conversation.split_message(9..9, cx));
3277        let message_4 = message_4.unwrap();
3278        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3279        assert_eq!(
3280            messages(&conversation, cx),
3281            vec![
3282                (message_1.id, Role::User, 0..4),
3283                (message_3.id, Role::User, 4..5),
3284                (message_2.id, Role::User, 5..9),
3285                (message_4.id, Role::User, 9..17),
3286            ]
3287        );
3288
3289        let (_, message_5) =
3290            conversation.update(cx, |conversation, cx| conversation.split_message(9..9, cx));
3291        let message_5 = message_5.unwrap();
3292        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
3293        assert_eq!(
3294            messages(&conversation, cx),
3295            vec![
3296                (message_1.id, Role::User, 0..4),
3297                (message_3.id, Role::User, 4..5),
3298                (message_2.id, Role::User, 5..9),
3299                (message_4.id, Role::User, 9..10),
3300                (message_5.id, Role::User, 10..18),
3301            ]
3302        );
3303
3304        let (message_6, message_7) = conversation.update(cx, |conversation, cx| {
3305            conversation.split_message(14..16, cx)
3306        });
3307        let message_6 = message_6.unwrap();
3308        let message_7 = message_7.unwrap();
3309        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
3310        assert_eq!(
3311            messages(&conversation, cx),
3312            vec![
3313                (message_1.id, Role::User, 0..4),
3314                (message_3.id, Role::User, 4..5),
3315                (message_2.id, Role::User, 5..9),
3316                (message_4.id, Role::User, 9..10),
3317                (message_5.id, Role::User, 10..14),
3318                (message_6.id, Role::User, 14..17),
3319                (message_7.id, Role::User, 17..19),
3320            ]
3321        );
3322    }
3323
3324    #[gpui::test]
3325    fn test_messages_for_offsets(cx: &mut AppContext) {
3326        let settings_store = SettingsStore::test(cx);
3327        cx.set_global(settings_store);
3328        init(cx);
3329        let registry = Arc::new(LanguageRegistry::test());
3330        let completion_provider = Arc::new(FakeCompletionProvider::new());
3331        let conversation =
3332            cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
3333        let buffer = conversation.read(cx).buffer.clone();
3334
3335        let message_1 = conversation.read(cx).message_anchors[0].clone();
3336        assert_eq!(
3337            messages(&conversation, cx),
3338            vec![(message_1.id, Role::User, 0..0)]
3339        );
3340
3341        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
3342        let message_2 = conversation
3343            .update(cx, |conversation, cx| {
3344                conversation.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
3345            })
3346            .unwrap();
3347        buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
3348
3349        let message_3 = conversation
3350            .update(cx, |conversation, cx| {
3351                conversation.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3352            })
3353            .unwrap();
3354        buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
3355
3356        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
3357        assert_eq!(
3358            messages(&conversation, cx),
3359            vec![
3360                (message_1.id, Role::User, 0..4),
3361                (message_2.id, Role::User, 4..8),
3362                (message_3.id, Role::User, 8..11)
3363            ]
3364        );
3365
3366        assert_eq!(
3367            message_ids_for_offsets(&conversation, &[0, 4, 9], cx),
3368            [message_1.id, message_2.id, message_3.id]
3369        );
3370        assert_eq!(
3371            message_ids_for_offsets(&conversation, &[0, 1, 11], cx),
3372            [message_1.id, message_3.id]
3373        );
3374
3375        let message_4 = conversation
3376            .update(cx, |conversation, cx| {
3377                conversation.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
3378            })
3379            .unwrap();
3380        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
3381        assert_eq!(
3382            messages(&conversation, cx),
3383            vec![
3384                (message_1.id, Role::User, 0..4),
3385                (message_2.id, Role::User, 4..8),
3386                (message_3.id, Role::User, 8..12),
3387                (message_4.id, Role::User, 12..12)
3388            ]
3389        );
3390        assert_eq!(
3391            message_ids_for_offsets(&conversation, &[0, 4, 8, 12], cx),
3392            [message_1.id, message_2.id, message_3.id, message_4.id]
3393        );
3394
3395        fn message_ids_for_offsets(
3396            conversation: &Model<Conversation>,
3397            offsets: &[usize],
3398            cx: &AppContext,
3399        ) -> Vec<MessageId> {
3400            conversation
3401                .read(cx)
3402                .messages_for_offsets(offsets.iter().copied(), cx)
3403                .into_iter()
3404                .map(|message| message.id)
3405                .collect()
3406        }
3407    }
3408
3409    #[gpui::test]
3410    fn test_serialization(cx: &mut AppContext) {
3411        let settings_store = SettingsStore::test(cx);
3412        cx.set_global(settings_store);
3413        init(cx);
3414        let registry = Arc::new(LanguageRegistry::test());
3415        let completion_provider = Arc::new(FakeCompletionProvider::new());
3416        let conversation =
3417            cx.build_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
3418        let buffer = conversation.read(cx).buffer.clone();
3419        let message_0 = conversation.read(cx).message_anchors[0].id;
3420        let message_1 = conversation.update(cx, |conversation, cx| {
3421            conversation
3422                .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
3423                .unwrap()
3424        });
3425        let message_2 = conversation.update(cx, |conversation, cx| {
3426            conversation
3427                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3428                .unwrap()
3429        });
3430        buffer.update(cx, |buffer, cx| {
3431            buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
3432            buffer.finalize_last_transaction();
3433        });
3434        let _message_3 = conversation.update(cx, |conversation, cx| {
3435            conversation
3436                .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
3437                .unwrap()
3438        });
3439        buffer.update(cx, |buffer, cx| buffer.undo(cx));
3440        assert_eq!(buffer.read(cx).text(), "a\nb\nc\n");
3441        assert_eq!(
3442            messages(&conversation, cx),
3443            [
3444                (message_0, Role::User, 0..2),
3445                (message_1.id, Role::Assistant, 2..6),
3446                (message_2.id, Role::System, 6..6),
3447            ]
3448        );
3449
3450        let deserialized_conversation = cx.build_model(|cx| {
3451            Conversation::deserialize(
3452                conversation.read(cx).serialize(cx),
3453                Default::default(),
3454                registry.clone(),
3455                cx,
3456            )
3457        });
3458        let deserialized_buffer = deserialized_conversation.read(cx).buffer.clone();
3459        assert_eq!(deserialized_buffer.read(cx).text(), "a\nb\nc\n");
3460        assert_eq!(
3461            messages(&deserialized_conversation, cx),
3462            [
3463                (message_0, Role::User, 0..2),
3464                (message_1.id, Role::Assistant, 2..6),
3465                (message_2.id, Role::System, 6..6),
3466            ]
3467        );
3468    }
3469
3470    fn messages(
3471        conversation: &Model<Conversation>,
3472        cx: &AppContext,
3473    ) -> Vec<(MessageId, Role, Range<usize>)> {
3474        conversation
3475            .read(cx)
3476            .messages(cx)
3477            .map(|message| (message.id, message.role, message.offset_range))
3478            .collect()
3479    }
3480}
3481
3482fn report_assistant_event(
3483    workspace: WeakView<Workspace>,
3484    conversation_id: Option<String>,
3485    assistant_kind: AssistantKind,
3486    cx: &AppContext,
3487) {
3488    let Some(workspace) = workspace.upgrade() else {
3489        return;
3490    };
3491
3492    let client = workspace.read(cx).project().read(cx).client();
3493    let telemetry = client.telemetry();
3494
3495    let model = AssistantSettings::get_global(cx)
3496        .default_open_ai_model
3497        .clone();
3498
3499    let telemetry_settings = TelemetrySettings::get_global(cx).clone();
3500
3501    telemetry.report_assistant_event(
3502        telemetry_settings,
3503        conversation_id,
3504        assistant_kind,
3505        model.full_name(),
3506    )
3507}