assistant.rs

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