assistant_panel.rs

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