assistant.rs

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