assistant_panel.rs

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