From f20f096a30f02dc565475d0c873f3a0a04ce9664 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 2 Oct 2023 19:15:59 +0300 Subject: [PATCH 01/17] searching the semantic index, and passing returned snippets to prompt generation --- Cargo.lock | 1 + crates/assistant/Cargo.toml | 2 + crates/assistant/src/assistant_panel.rs | 57 +++++++++++++++++++++++-- crates/assistant/src/prompts.rs | 1 + 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76de671620..2b7d74578d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ "regex", "schemars", "search", + "semantic_index", "serde", "serde_json", "settings", diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 5d141b32d5..8b69e82109 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -23,7 +23,9 @@ theme = { path = "../theme" } util = { path = "../util" } uuid = { version = "1.1.2", features = ["v4"] } workspace = { path = "../workspace" } +semantic_index = { path = "../semantic_index" } +log.workspace = true anyhow.workspace = true chrono = { version = "0.4", features = ["serde"] } futures.workspace = true diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b69c12a2a3..8fa0327134 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -36,6 +36,7 @@ use gpui::{ }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use search::BufferSearchBar; +use semantic_index::SemanticIndex; use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, @@ -145,6 +146,7 @@ pub struct AssistantPanel { include_conversation_in_next_inline_assist: bool, inline_prompt_history: VecDeque, _watch_saved_conversations: Task>, + semantic_index: Option>, } impl AssistantPanel { @@ -191,6 +193,9 @@ impl AssistantPanel { toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx); toolbar }); + + let semantic_index = SemanticIndex::global(cx); + let mut this = Self { workspace: workspace_handle, active_editor_index: Default::default(), @@ -215,6 +220,7 @@ impl AssistantPanel { include_conversation_in_next_inline_assist: false, inline_prompt_history: Default::default(), _watch_saved_conversations, + semantic_index, }; let mut old_dock_position = this.position(cx); @@ -578,10 +584,55 @@ impl AssistantPanel { let codegen_kind = codegen.read(cx).kind().clone(); let user_prompt = user_prompt.to_string(); - let prompt = cx.background().spawn(async move { - let language_name = language_name.as_deref(); - generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind) + + let project = if let Some(workspace) = self.workspace.upgrade(cx) { + workspace.read(cx).project() + } else { + return; + }; + + let project = project.to_owned(); + let search_results = if let Some(semantic_index) = self.semantic_index.clone() { + let search_results = semantic_index.update(cx, |this, cx| { + this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) + }); + + cx.background() + .spawn(async move { search_results.await.unwrap_or_default() }) + } else { + Task::ready(Vec::new()) + }; + + let snippets = cx.spawn(|_, cx| async move { + let mut snippets = Vec::new(); + for result in search_results.await { + snippets.push(result.buffer.read_with(&cx, |buffer, _| { + buffer + .snapshot() + .text_for_range(result.range) + .collect::() + })); + } + snippets }); + + let prompt = cx.background().spawn(async move { + let snippets = snippets.await; + for snippet in &snippets { + println!("SNIPPET: \n{:?}", snippet); + } + + let language_name = language_name.as_deref(); + generate_content_prompt( + user_prompt, + language_name, + &buffer, + range, + codegen_kind, + snippets, + ) + }); + let mut messages = Vec::new(); let mut model = settings::get::(cx) .default_open_ai_model diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 2451369a18..2301cd88ff 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -121,6 +121,7 @@ pub fn generate_content_prompt( buffer: &BufferSnapshot, range: Range, kind: CodegenKind, + search_results: Vec, ) -> String { let mut prompt = String::new(); From e9637267efb636e3da4b08570ed3b64cc17dce02 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 2 Oct 2023 19:50:57 +0300 Subject: [PATCH 02/17] add placeholder button for retrieving additional context --- crates/assistant/src/assistant_panel.rs | 34 +++++++++++++++ crates/theme/src/theme.rs | 1 + styles/src/style_tree/assistant.ts | 56 +++++++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 8fa0327134..8cba4c4d9f 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -73,6 +73,7 @@ actions!( ResetKey, InlineAssist, ToggleIncludeConversation, + ToggleRetrieveContext, ] ); @@ -109,6 +110,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(InlineAssistant::confirm); cx.add_action(InlineAssistant::cancel); cx.add_action(InlineAssistant::toggle_include_conversation); + cx.add_action(InlineAssistant::toggle_retrieve_context); cx.add_action(InlineAssistant::move_up); cx.add_action(InlineAssistant::move_down); } @@ -147,6 +149,7 @@ pub struct AssistantPanel { inline_prompt_history: VecDeque, _watch_saved_conversations: Task>, semantic_index: Option>, + retrieve_context_in_next_inline_assist: bool, } impl AssistantPanel { @@ -221,6 +224,7 @@ impl AssistantPanel { inline_prompt_history: Default::default(), _watch_saved_conversations, semantic_index, + retrieve_context_in_next_inline_assist: false, }; let mut old_dock_position = this.position(cx); @@ -314,6 +318,7 @@ impl AssistantPanel { codegen.clone(), self.workspace.clone(), cx, + self.retrieve_context_in_next_inline_assist, ); cx.focus_self(); assistant @@ -446,6 +451,9 @@ impl AssistantPanel { } => { self.include_conversation_in_next_inline_assist = *include_conversation; } + InlineAssistantEvent::RetrieveContextToggled { retrieve_context } => { + self.retrieve_context_in_next_inline_assist = *retrieve_context + } } } @@ -2679,6 +2687,9 @@ enum InlineAssistantEvent { IncludeConversationToggled { include_conversation: bool, }, + RetrieveContextToggled { + retrieve_context: bool, + }, } struct InlineAssistant { @@ -2694,6 +2705,7 @@ struct InlineAssistant { pending_prompt: String, codegen: ModelHandle, _subscriptions: Vec, + retrieve_context: bool, } impl Entity for InlineAssistant { @@ -2722,6 +2734,18 @@ impl View for InlineAssistant { .element() .aligned(), ) + .with_child( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) + .element() + .aligned(), + ) .with_children(if let Some(error) = self.codegen.read(cx).error() { Some( Svg::new("icons/error.svg") @@ -2802,6 +2826,7 @@ impl InlineAssistant { codegen: ModelHandle, workspace: WeakViewHandle, cx: &mut ViewContext, + retrieve_context: bool, ) -> Self { let prompt_editor = cx.add_view(|cx| { let mut editor = Editor::single_line( @@ -2832,6 +2857,7 @@ impl InlineAssistant { pending_prompt: String::new(), codegen, _subscriptions: subscriptions, + retrieve_context, } } @@ -2902,6 +2928,14 @@ impl InlineAssistant { } } + fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext) { + self.retrieve_context = !self.retrieve_context; + cx.emit(InlineAssistantEvent::RetrieveContextToggled { + retrieve_context: self.retrieve_context, + }); + cx.notify(); + } + fn toggle_include_conversation( &mut self, _: &ToggleIncludeConversation, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 5ea5ce8778..1ebdcd0ba6 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1190,6 +1190,7 @@ pub struct InlineAssistantStyle { pub disabled_editor: FieldEditor, pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, + pub retrieve_context: ToggleIconButtonStyle, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index cc6ee4b080..7fd1388d9c 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -79,6 +79,62 @@ export default function assistant(): any { }, }, pending_edit_background: background(theme.highest, "positive"), + retrieve_context: toggleable({ + base: interactive({ + base: { + icon_size: 12, + color: foreground(theme.highest, "variant"), + + button_width: 12, + background: background(theme.highest, "on"), + corner_radius: 2, + border: { + width: 1., color: background(theme.highest, "on") + }, + padding: { + left: 4, + right: 4, + top: 4, + bottom: 4, + }, + }, + state: { + hovered: { + ...text(theme.highest, "mono", "variant", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: { + width: 1., color: background(theme.highest, "on", "hovered") + }, + }, + clicked: { + ...text(theme.highest, "mono", "variant", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: { + width: 1., color: background(theme.highest, "on", "pressed") + }, + }, + }, + }), + state: { + active: { + default: { + icon_size: 12, + button_width: 12, + color: foreground(theme.highest, "variant"), + background: background(theme.highest, "accent"), + border: border(theme.highest, "accent"), + }, + hovered: { + background: background(theme.highest, "accent", "hovered"), + border: border(theme.highest, "accent", "hovered"), + }, + clicked: { + background: background(theme.highest, "accent", "pressed"), + border: border(theme.highest, "accent", "pressed"), + }, + }, + }, + }), include_conversation: toggleable({ base: interactive({ base: { From bfe76467b03c23f30a00a3055c0699a7dc171615 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 11:19:54 +0300 Subject: [PATCH 03/17] add retrieve context button to inline assistant --- Cargo.lock | 21 +---- crates/assistant/Cargo.toml | 2 +- crates/assistant/src/assistant_panel.rs | 89 +++++++++++-------- crates/assistant/src/prompts.rs | 112 ++++++++++++++++-------- 4 files changed, 131 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b7d74578d..92d17ec0db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", - "tiktoken-rs 0.5.4", + "tiktoken-rs", "util", ] @@ -327,7 +327,7 @@ dependencies = [ "settings", "smol", "theme", - "tiktoken-rs 0.4.5", + "tiktoken-rs", "util", "uuid 1.4.1", "workspace", @@ -6798,7 +6798,7 @@ dependencies = [ "smol", "tempdir", "theme", - "tiktoken-rs 0.5.4", + "tiktoken-rs", "tree-sitter", "tree-sitter-cpp", "tree-sitter-elixir", @@ -7875,21 +7875,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "tiktoken-rs" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52aacc1cff93ba9d5f198c62c49c77fa0355025c729eed3326beaf7f33bc8614" -dependencies = [ - "anyhow", - "base64 0.21.4", - "bstr", - "fancy-regex", - "lazy_static", - "parking_lot 0.12.1", - "rustc-hash", -] - [[package]] name = "tiktoken-rs" version = "0.5.4" diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 8b69e82109..12f52eee02 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -38,7 +38,7 @@ schemars.workspace = true serde.workspace = true serde_json.workspace = true smol.workspace = true -tiktoken-rs = "0.4" +tiktoken-rs = "0.5" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 8cba4c4d9f..16d7ee6b81 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -437,8 +437,15 @@ impl AssistantPanel { InlineAssistantEvent::Confirmed { prompt, include_conversation, + retrieve_context, } => { - self.confirm_inline_assist(assist_id, prompt, *include_conversation, cx); + self.confirm_inline_assist( + assist_id, + prompt, + *include_conversation, + cx, + *retrieve_context, + ); } InlineAssistantEvent::Canceled => { self.finish_inline_assist(assist_id, true, cx); @@ -532,6 +539,7 @@ impl AssistantPanel { user_prompt: &str, include_conversation: bool, cx: &mut ViewContext, + retrieve_context: bool, ) { let conversation = if include_conversation { self.active_editor() @@ -593,42 +601,49 @@ impl AssistantPanel { let codegen_kind = codegen.read(cx).kind().clone(); let user_prompt = user_prompt.to_string(); - let project = if let Some(workspace) = self.workspace.upgrade(cx) { - workspace.read(cx).project() - } else { - return; - }; + let snippets = if retrieve_context { + let project = if let Some(workspace) = self.workspace.upgrade(cx) { + workspace.read(cx).project() + } else { + return; + }; - let project = project.to_owned(); - let search_results = if let Some(semantic_index) = self.semantic_index.clone() { - let search_results = semantic_index.update(cx, |this, cx| { - this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) + let project = project.to_owned(); + let search_results = if let Some(semantic_index) = self.semantic_index.clone() { + let search_results = semantic_index.update(cx, |this, cx| { + this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) + }); + + cx.background() + .spawn(async move { search_results.await.unwrap_or_default() }) + } else { + Task::ready(Vec::new()) + }; + + let snippets = cx.spawn(|_, cx| async move { + let mut snippets = Vec::new(); + for result in search_results.await { + snippets.push(result.buffer.read_with(&cx, |buffer, _| { + buffer + .snapshot() + .text_for_range(result.range) + .collect::() + })); + } + snippets }); - - cx.background() - .spawn(async move { search_results.await.unwrap_or_default() }) + snippets } else { Task::ready(Vec::new()) }; - let snippets = cx.spawn(|_, cx| async move { - let mut snippets = Vec::new(); - for result in search_results.await { - snippets.push(result.buffer.read_with(&cx, |buffer, _| { - buffer - .snapshot() - .text_for_range(result.range) - .collect::() - })); - } - snippets - }); + let mut model = settings::get::(cx) + .default_open_ai_model + .clone(); + let model_name = model.full_name(); let prompt = cx.background().spawn(async move { let snippets = snippets.await; - for snippet in &snippets { - println!("SNIPPET: \n{:?}", snippet); - } let language_name = language_name.as_deref(); generate_content_prompt( @@ -638,13 +653,11 @@ impl AssistantPanel { range, codegen_kind, snippets, + model_name, ) }); let mut messages = Vec::new(); - let mut model = settings::get::(cx) - .default_open_ai_model - .clone(); if let Some(conversation) = conversation { let conversation = conversation.read(cx); let buffer = conversation.buffer.read(cx); @@ -1557,12 +1570,14 @@ impl Conversation { Role::Assistant => "assistant".into(), Role::System => "system".into(), }, - content: self - .buffer - .read(cx) - .text_for_range(message.offset_range) - .collect(), + content: Some( + self.buffer + .read(cx) + .text_for_range(message.offset_range) + .collect(), + ), name: None, + function_call: None, }) }) .collect::>(); @@ -2681,6 +2696,7 @@ enum InlineAssistantEvent { Confirmed { prompt: String, include_conversation: bool, + retrieve_context: bool, }, Canceled, Dismissed, @@ -2922,6 +2938,7 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::Confirmed { prompt, include_conversation: self.include_conversation, + retrieve_context: self.retrieve_context, }); self.confirmed = true; cx.notify(); diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 2301cd88ff..1e43833fea 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -1,8 +1,10 @@ use crate::codegen::CodegenKind; use language::{BufferSnapshot, OffsetRangeExt, ToOffset}; use std::cmp; +use std::fmt::Write; +use std::iter; use std::ops::Range; -use std::{fmt::Write, iter}; +use tiktoken_rs::ChatCompletionRequestMessage; fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> String { #[derive(Debug)] @@ -122,69 +124,103 @@ pub fn generate_content_prompt( range: Range, kind: CodegenKind, search_results: Vec, + model: &str, ) -> String { - let mut prompt = String::new(); + const MAXIMUM_SNIPPET_TOKEN_COUNT: usize = 500; + + let mut prompts = Vec::new(); // General Preamble if let Some(language_name) = language_name { - writeln!(prompt, "You're an expert {language_name} engineer.\n").unwrap(); + prompts.push(format!("You're an expert {language_name} engineer.\n")); } else { - writeln!(prompt, "You're an expert engineer.\n").unwrap(); + prompts.push("You're an expert engineer.\n".to_string()); } + // Snippets + let mut snippet_position = prompts.len() - 1; + let outline = summarize(buffer, range); - writeln!( - prompt, - "The file you are currently working on has the following outline:" - ) - .unwrap(); + prompts.push("The file you are currently working on has the following outline:".to_string()); if let Some(language_name) = language_name { let language_name = language_name.to_lowercase(); - writeln!(prompt, "```{language_name}\n{outline}\n```").unwrap(); + prompts.push(format!("```{language_name}\n{outline}\n```")); } else { - writeln!(prompt, "```\n{outline}\n```").unwrap(); + prompts.push(format!("```\n{outline}\n```")); } match kind { CodegenKind::Generate { position: _ } => { - writeln!(prompt, "In particular, the user's cursor is current on the '<|START|>' span in the above outline, with no text selected.").unwrap(); - writeln!( - prompt, - "Assume the cursor is located where the `<|START|` marker is." - ) - .unwrap(); - writeln!( - prompt, + prompts.push("In particular, the user's cursor is currently on the '<|START|>' span in the above outline, with no text selected.".to_string()); + prompts + .push("Assume the cursor is located where the `<|START|` marker is.".to_string()); + prompts.push( "Text can't be replaced, so assume your answer will be inserted at the cursor." - ) - .unwrap(); - writeln!( - prompt, + .to_string(), + ); + prompts.push(format!( "Generate text based on the users prompt: {user_prompt}" - ) - .unwrap(); + )); } CodegenKind::Transform { range: _ } => { - writeln!(prompt, "In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.").unwrap(); - writeln!( - prompt, + prompts.push("In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.".to_string()); + prompts.push(format!( "Modify the users code selected text based upon the users prompt: {user_prompt}" - ) - .unwrap(); - writeln!( - prompt, - "You MUST reply with only the adjusted code (within the '<|START|' and '|END|>' spans), not the entire file." - ) - .unwrap(); + )); + prompts.push("You MUST reply with only the adjusted code (within the '<|START|' and '|END|>' spans), not the entire file.".to_string()); } } if let Some(language_name) = language_name { - writeln!(prompt, "Your answer MUST always be valid {language_name}").unwrap(); + prompts.push(format!("Your answer MUST always be valid {language_name}")); } - writeln!(prompt, "Always wrap your response in a Markdown codeblock").unwrap(); - writeln!(prompt, "Never make remarks about the output.").unwrap(); + prompts.push("Always wrap your response in a Markdown codeblock".to_string()); + prompts.push("Never make remarks about the output.".to_string()); + let current_messages = [ChatCompletionRequestMessage { + role: "user".to_string(), + content: Some(prompts.join("\n")), + function_call: None, + name: None, + }]; + + let remaining_token_count = if let Ok(current_token_count) = + tiktoken_rs::num_tokens_from_messages(model, ¤t_messages) + { + let max_token_count = tiktoken_rs::model::get_context_size(model); + max_token_count - current_token_count + } else { + // If tiktoken fails to count token count, assume we have no space remaining. + 0 + }; + + // TODO: + // - add repository name to snippet + // - add file path + // - add language + if let Ok(encoding) = tiktoken_rs::get_bpe_from_model(model) { + let template = "You are working inside a large repository, here are a few code snippets that may be useful"; + + for search_result in search_results { + let mut snippet_prompt = template.to_string(); + writeln!(snippet_prompt, "```\n{search_result}\n```").unwrap(); + + let token_count = encoding + .encode_with_special_tokens(snippet_prompt.as_str()) + .len(); + if token_count <= remaining_token_count { + if token_count < MAXIMUM_SNIPPET_TOKEN_COUNT { + prompts.insert(snippet_position, snippet_prompt); + snippet_position += 1; + } + } else { + break; + } + } + } + + let prompt = prompts.join("\n"); + println!("PROMPT: {:?}", prompt); prompt } From ed894cc06fc010ae6ea15880d02923130a669e11 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 12:09:35 +0300 Subject: [PATCH 04/17] only render retrieve context button if semantic index is enabled --- crates/assistant/src/assistant_panel.rs | 28 ++++++++++++++----------- crates/assistant/src/prompts.rs | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 16d7ee6b81..33d42c45dc 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2750,18 +2750,22 @@ impl View for InlineAssistant { .element() .aligned(), ) - .with_child( - Button::action(ToggleRetrieveContext) - .with_tooltip("Retrieve Context", theme.tooltip.clone()) - .with_id(self.id) - .with_contents(theme::components::svg::Svg::new( - "icons/magnifying_glass.svg", - )) - .toggleable(self.retrieve_context) - .with_style(theme.assistant.inline.retrieve_context.clone()) - .element() - .aligned(), - ) + .with_children(if SemanticIndex::enabled(cx) { + Some( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) + .element() + .aligned(), + ) + } else { + None + }) .with_children(if let Some(error) = self.codegen.read(cx).error() { Some( Svg::new("icons/error.svg") diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 716fd43505..487950dbef 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -2,7 +2,6 @@ use crate::codegen::CodegenKind; use language::{BufferSnapshot, OffsetRangeExt, ToOffset}; use std::cmp::{self, Reverse}; use std::fmt::Write; -use std::iter; use std::ops::Range; use tiktoken_rs::ChatCompletionRequestMessage; From 1a2756a2325ddea88d8f8679b8022a8f17d97a30 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 14:07:42 +0300 Subject: [PATCH 05/17] start greedily indexing when inline assistant is started, if project has been previously indexed --- crates/assistant/Cargo.toml | 1 + crates/assistant/src/assistant_panel.rs | 47 ++++++++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 12f52eee02..e0f90a4284 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -24,6 +24,7 @@ util = { path = "../util" } uuid = { version = "1.1.2", features = ["v4"] } workspace = { path = "../workspace" } semantic_index = { path = "../semantic_index" } +project = { path = "../project" } log.workspace = true anyhow.workspace = true diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 33d42c45dc..be46a63c8f 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -31,10 +31,11 @@ use gpui::{ geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, - ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, + WeakModelHandle, WeakViewHandle, WindowContext, }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; +use project::Project; use search::BufferSearchBar; use semantic_index::SemanticIndex; use settings::SettingsStore; @@ -272,12 +273,19 @@ impl AssistantPanel { return; }; + let project = workspace.project(); + this.update(cx, |assistant, cx| { - assistant.new_inline_assist(&active_editor, cx) + assistant.new_inline_assist(&active_editor, cx, project) }); } - fn new_inline_assist(&mut self, editor: &ViewHandle, cx: &mut ViewContext) { + fn new_inline_assist( + &mut self, + editor: &ViewHandle, + cx: &mut ViewContext, + project: &ModelHandle, + ) { let api_key = if let Some(api_key) = self.api_key.borrow().clone() { api_key } else { @@ -308,6 +316,27 @@ impl AssistantPanel { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) }); + if let Some(semantic_index) = self.semantic_index.clone() { + let project = project.clone(); + cx.spawn(|_, mut cx| async move { + let previously_indexed = semantic_index + .update(&mut cx, |index, cx| { + index.project_previously_indexed(&project, cx) + }) + .await + .unwrap_or(false); + if previously_indexed { + let _ = semantic_index + .update(&mut cx, |index, cx| { + index.index_project(project.clone(), cx) + }) + .await; + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + let measurements = Rc::new(Cell::new(BlockMeasurements::default())); let inline_assistant = cx.add_view(|cx| { let assistant = InlineAssistant::new( @@ -359,6 +388,7 @@ impl AssistantPanel { editor: editor.downgrade(), inline_assistant: Some((block_id, inline_assistant.clone())), codegen: codegen.clone(), + project: project.downgrade(), _subscriptions: vec![ cx.subscribe(&inline_assistant, Self::handle_inline_assistant_event), cx.subscribe(editor, { @@ -561,6 +591,8 @@ impl AssistantPanel { return; }; + let project = pending_assist.project.clone(); + self.inline_prompt_history .retain(|prompt| prompt != user_prompt); self.inline_prompt_history.push_back(user_prompt.into()); @@ -602,13 +634,10 @@ impl AssistantPanel { let user_prompt = user_prompt.to_string(); let snippets = if retrieve_context { - let project = if let Some(workspace) = self.workspace.upgrade(cx) { - workspace.read(cx).project() - } else { + let Some(project) = project.upgrade(cx) else { return; }; - let project = project.to_owned(); let search_results = if let Some(semantic_index) = self.semantic_index.clone() { let search_results = semantic_index.update(cx, |this, cx| { this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) @@ -2864,6 +2893,7 @@ impl InlineAssistant { cx.observe(&codegen, Self::handle_codegen_changed), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), ]; + Self { id, prompt_editor, @@ -3019,6 +3049,7 @@ struct PendingInlineAssist { inline_assistant: Option<(BlockId, ViewHandle)>, codegen: ModelHandle, _subscriptions: Vec, + project: WeakModelHandle, } fn merge_ranges(ranges: &mut Vec>, buffer: &MultiBufferSnapshot) { From f40d3e82c0dbef9633c86bcb9175a4908206f222 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 16:26:08 +0300 Subject: [PATCH 06/17] add user prompt for permission to index the project, for context retrieval --- crates/assistant/src/assistant_panel.rs | 77 +++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index be46a63c8f..99151e5ac2 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -29,7 +29,7 @@ use gpui::{ }, fonts::HighlightStyle, geometry::vector::{vec2f, Vector2F}, - platform::{CursorStyle, MouseButton}, + platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, WindowContext, @@ -348,6 +348,8 @@ impl AssistantPanel { self.workspace.clone(), cx, self.retrieve_context_in_next_inline_assist, + self.semantic_index.clone(), + project.clone(), ); cx.focus_self(); assistant @@ -2751,6 +2753,9 @@ struct InlineAssistant { codegen: ModelHandle, _subscriptions: Vec, retrieve_context: bool, + semantic_index: Option>, + semantic_permissioned: Option, + project: ModelHandle, } impl Entity for InlineAssistant { @@ -2876,6 +2881,8 @@ impl InlineAssistant { workspace: WeakViewHandle, cx: &mut ViewContext, retrieve_context: bool, + semantic_index: Option>, + project: ModelHandle, ) -> Self { let prompt_editor = cx.add_view(|cx| { let mut editor = Editor::single_line( @@ -2908,9 +2915,26 @@ impl InlineAssistant { codegen, _subscriptions: subscriptions, retrieve_context, + semantic_permissioned: None, + semantic_index, + project, } } + fn semantic_permissioned(&mut self, cx: &mut ViewContext) -> Task> { + if let Some(value) = self.semantic_permissioned { + return Task::ready(Ok(value)); + } + + let project = self.project.clone(); + self.semantic_index + .as_mut() + .map(|semantic| { + semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx)) + }) + .unwrap_or(Task::ready(Ok(false))) + } + fn handle_prompt_editor_events( &mut self, _: ViewHandle, @@ -2980,11 +3004,52 @@ impl InlineAssistant { } fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext) { - self.retrieve_context = !self.retrieve_context; - cx.emit(InlineAssistantEvent::RetrieveContextToggled { - retrieve_context: self.retrieve_context, - }); - cx.notify(); + let semantic_permissioned = self.semantic_permissioned(cx); + let project = self.project.clone(); + let project_name = project + .read(cx) + .worktree_root_names(cx) + .collect::>() + .join("/"); + let is_plural = project_name.chars().filter(|letter| *letter == '/').count() > 0; + let prompt_text = format!("Would you like to index the '{}' project{} for context retrieval? This requires sending code to the OpenAI API", project_name, + if is_plural { + "s" + } else {""}); + + cx.spawn(|this, mut cx| async move { + // If Necessary prompt user + if !semantic_permissioned.await.unwrap_or(false) { + let mut answer = this.update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Info, + prompt_text.as_str(), + &["Continue", "Cancel"], + ) + })?; + + if answer.next().await == Some(0) { + this.update(&mut cx, |this, _| { + this.semantic_permissioned = Some(true); + })?; + } else { + return anyhow::Ok(()); + } + } + + // If permissioned, update context appropriately + this.update(&mut cx, |this, cx| { + this.retrieve_context = !this.retrieve_context; + + cx.emit(InlineAssistantEvent::RetrieveContextToggled { + retrieve_context: this.retrieve_context, + }); + cx.notify(); + })?; + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } fn toggle_include_conversation( From 933c21f3d3dead2cd0717fe289aff5bda1784edd Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 16:53:57 +0300 Subject: [PATCH 07/17] add initial (non updating status) toast --- crates/assistant/src/assistant_panel.rs | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 99151e5ac2..e6c120cd64 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -48,7 +48,7 @@ use std::{ path::{Path, PathBuf}, rc::Rc, sync::Arc, - time::Duration, + time::{Duration, Instant}, }; use theme::{ components::{action_button::Button, ComponentExt}, @@ -3044,6 +3044,16 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); + + if this.retrieve_context { + let context_status = this.retrieve_context_status(cx); + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + workspace.show_toast(Toast::new(0, context_status), cx) + }); + } + } + cx.notify(); })?; @@ -3052,6 +3062,40 @@ impl InlineAssistant { .detach_and_log_err(cx); } + fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { + let project = self.project.clone(); + if let Some(semantic_index) = self.semantic_index.clone() { + let status = semantic_index.update(cx, |index, cx| index.status(&project)); + return match status { + // This theoretically shouldnt be a valid code path + semantic_index::SemanticIndexStatus::NotAuthenticated => { + "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() + } + semantic_index::SemanticIndexStatus::Indexed => { + "Indexing for Context Retrieval Complete!".to_string() + } + semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { + + let mut status = format!("Indexing for Context Retrieval...\nRemaining files to index: {remaining_files}"); + + if let Some(rate_limit_expiry) = rate_limit_expiry { + let remaining_seconds = + rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) { + writeln!(status, "Rate limit resets in {}s", remaining_seconds.as_secs()).unwrap(); + } + } + status + } + _ => { + "Indexing for Context Retrieval...\nRemaining files to index: 48".to_string() + } + }; + } + + "".to_string() + } + fn toggle_include_conversation( &mut self, _: &ToggleIncludeConversation, From ec1b4e6f8563d52eaa96977cb780fcd6be61c2c1 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 5 Oct 2023 13:01:11 +0300 Subject: [PATCH 08/17] added initial working status in inline assistant prompt --- crates/ai/src/embedding.rs | 2 +- crates/assistant/src/assistant_panel.rs | 200 +++++++++++++++--------- crates/theme/src/theme.rs | 1 + styles/src/style_tree/assistant.ts | 3 + 4 files changed, 129 insertions(+), 77 deletions(-) diff --git a/crates/ai/src/embedding.rs b/crates/ai/src/embedding.rs index 332470aa54..510f987cca 100644 --- a/crates/ai/src/embedding.rs +++ b/crates/ai/src/embedding.rs @@ -290,7 +290,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { let mut request_number = 0; let mut rate_limiting = false; - let mut request_timeout: u64 = 15; + let mut request_timeout: u64 = 30; let mut response: Response; while request_number < MAX_RETRIES { response = self diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e6c120cd64..c49d60b8ee 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -24,10 +24,10 @@ use futures::StreamExt; use gpui::{ actions, elements::{ - ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable, - Stack, Svg, Text, UniformList, UniformListState, + ChildView, Component, Empty, Flex, Label, LabelStyle, MouseEventHandler, ParentElement, + SafeStylable, Stack, Svg, Text, UniformList, UniformListState, }, - fonts::HighlightStyle, + fonts::{HighlightStyle, TextStyle}, geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, @@ -37,7 +37,7 @@ use gpui::{ use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; use search::BufferSearchBar; -use semantic_index::SemanticIndex; +use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, @@ -2756,6 +2756,7 @@ struct InlineAssistant { semantic_index: Option>, semantic_permissioned: Option, project: ModelHandle, + maintain_rate_limit: Option>, } impl Entity for InlineAssistant { @@ -2772,67 +2773,65 @@ impl View for InlineAssistant { let theme = theme::current(cx); Flex::row() - .with_child( - Flex::row() - .with_child( - Button::action(ToggleIncludeConversation) - .with_tooltip("Include Conversation", theme.tooltip.clone()) + .with_children([Flex::row() + .with_child( + Button::action(ToggleIncludeConversation) + .with_tooltip("Include Conversation", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new("icons/ai.svg")) + .toggleable(self.include_conversation) + .with_style(theme.assistant.inline.include_conversation.clone()) + .element() + .aligned(), + ) + .with_children(if SemanticIndex::enabled(cx) { + Some( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) .with_id(self.id) - .with_contents(theme::components::svg::Svg::new("icons/ai.svg")) - .toggleable(self.include_conversation) - .with_style(theme.assistant.inline.include_conversation.clone()) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) .element() .aligned(), ) - .with_children(if SemanticIndex::enabled(cx) { - Some( - Button::action(ToggleRetrieveContext) - .with_tooltip("Retrieve Context", theme.tooltip.clone()) - .with_id(self.id) - .with_contents(theme::components::svg::Svg::new( - "icons/magnifying_glass.svg", - )) - .toggleable(self.retrieve_context) - .with_style(theme.assistant.inline.retrieve_context.clone()) - .element() - .aligned(), - ) - } else { - None - }) - .with_children(if let Some(error) = self.codegen.read(cx).error() { - Some( - Svg::new("icons/error.svg") - .with_color(theme.assistant.error_icon.color) - .constrained() - .with_width(theme.assistant.error_icon.width) - .contained() - .with_style(theme.assistant.error_icon.container) - .with_tooltip::( - self.id, - error.to_string(), - None, - theme.tooltip.clone(), - cx, - ) - .aligned(), - ) - } else { - None - }) - .aligned() - .constrained() - .dynamically({ - let measurements = self.measurements.clone(); - move |constraint, _, _| { - let measurements = measurements.get(); - SizeConstraint { - min: vec2f(measurements.gutter_width, constraint.min.y()), - max: vec2f(measurements.gutter_width, constraint.max.y()), - } + } else { + None + }) + .with_children(if let Some(error) = self.codegen.read(cx).error() { + Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.error_icon.color) + .constrained() + .with_width(theme.assistant.error_icon.width) + .contained() + .with_style(theme.assistant.error_icon.container) + .with_tooltip::( + self.id, + error.to_string(), + None, + theme.tooltip.clone(), + cx, + ) + .aligned(), + ) + } else { + None + }) + .aligned() + .constrained() + .dynamically({ + let measurements = self.measurements.clone(); + move |constraint, _, _| { + let measurements = measurements.get(); + SizeConstraint { + min: vec2f(measurements.gutter_width, constraint.min.y()), + max: vec2f(measurements.gutter_width, constraint.max.y()), } - }), - ) + } + })]) .with_child(Empty::new().constrained().dynamically({ let measurements = self.measurements.clone(); move |constraint, _, _| { @@ -2855,6 +2854,19 @@ impl View for InlineAssistant { .left() .flex(1., true), ) + .with_children(if self.retrieve_context { + Some( + Flex::row() + .with_child(Label::new( + self.retrieve_context_status(cx), + theme.assistant.inline.context_status.text.clone(), + )) + .flex(1., true) + .aligned(), + ) + } else { + None + }) .contained() .with_style(theme.assistant.inline.container) .into_any() @@ -2896,11 +2908,15 @@ impl InlineAssistant { editor.set_placeholder_text(placeholder, cx); editor }); - let subscriptions = vec![ + let mut subscriptions = vec![ cx.observe(&codegen, Self::handle_codegen_changed), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), ]; + if let Some(semantic_index) = semantic_index.clone() { + subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed)); + } + Self { id, prompt_editor, @@ -2918,6 +2934,7 @@ impl InlineAssistant { semantic_permissioned: None, semantic_index, project, + maintain_rate_limit: None, } } @@ -2947,6 +2964,34 @@ impl InlineAssistant { } } + fn semantic_index_changed( + &mut self, + semantic_index: ModelHandle, + cx: &mut ViewContext, + ) { + let project = self.project.clone(); + let status = semantic_index.read(cx).status(&project); + match status { + SemanticIndexStatus::Indexing { + rate_limit_expiry: Some(_), + .. + } => { + if self.maintain_rate_limit.is_none() { + self.maintain_rate_limit = Some(cx.spawn(|this, mut cx| async move { + loop { + cx.background().timer(Duration::from_secs(1)).await; + this.update(&mut cx, |_, cx| cx.notify()).log_err(); + } + })); + } + return; + } + _ => { + self.maintain_rate_limit = None; + } + } + } + fn handle_codegen_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { let is_read_only = !self.codegen.read(cx).idle(); self.prompt_editor.update(cx, |editor, cx| { @@ -3044,16 +3089,7 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); - - if this.retrieve_context { - let context_status = this.retrieve_context_status(cx); - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - workspace.show_toast(Toast::new(0, context_status), cx) - }); - } - } - + this.index_project(project, cx).log_err(); cx.notify(); })?; @@ -3062,6 +3098,18 @@ impl InlineAssistant { .detach_and_log_err(cx); } + fn index_project( + &self, + project: ModelHandle, + cx: &mut ViewContext, + ) -> anyhow::Result<()> { + if let Some(semantic_index) = self.semantic_index.clone() { + let _ = semantic_index.update(cx, |index, cx| index.index_project(project, cx)); + } + + anyhow::Ok(()) + } + fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { let project = self.project.clone(); if let Some(semantic_index) = self.semantic_index.clone() { @@ -3072,23 +3120,23 @@ impl InlineAssistant { "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() } semantic_index::SemanticIndexStatus::Indexed => { - "Indexing for Context Retrieval Complete!".to_string() + "Indexing Complete!".to_string() } semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { - let mut status = format!("Indexing for Context Retrieval...\nRemaining files to index: {remaining_files}"); + let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); if let Some(rate_limit_expiry) = rate_limit_expiry { let remaining_seconds = rate_limit_expiry.duration_since(Instant::now()); if remaining_seconds > Duration::from_secs(0) { - writeln!(status, "Rate limit resets in {}s", remaining_seconds.as_secs()).unwrap(); + write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); } } status } - _ => { - "Indexing for Context Retrieval...\nRemaining files to index: 48".to_string() + semantic_index::SemanticIndexStatus::NotIndexed => { + "Not Indexed for Context Retrieval".to_string() } }; } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 600ac7f14a..4ed32b6d1b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1191,6 +1191,7 @@ pub struct InlineAssistantStyle { pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, pub retrieve_context: ToggleIconButtonStyle, + pub context_status: ContainedText, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 7fd1388d9c..7e7b597956 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -79,6 +79,9 @@ export default function assistant(): any { }, }, pending_edit_background: background(theme.highest, "positive"), + context_status: { + ...text(theme.highest, "mono", "disabled", { size: "sm" }), + }, retrieve_context: toggleable({ base: interactive({ base: { From 0666fa80ac934f91a744988b405be9e80a4ccfb3 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 5 Oct 2023 16:49:25 +0300 Subject: [PATCH 09/17] moved status to icon with additional information in tooltip --- crates/ai/src/embedding.rs | 22 +--- crates/assistant/src/assistant_panel.rs | 164 +++++++++++++++++++----- crates/theme/src/theme.rs | 9 +- styles/src/style_tree/assistant.ts | 17 ++- 4 files changed, 161 insertions(+), 51 deletions(-) diff --git a/crates/ai/src/embedding.rs b/crates/ai/src/embedding.rs index 510f987cca..4587ece0a2 100644 --- a/crates/ai/src/embedding.rs +++ b/crates/ai/src/embedding.rs @@ -85,25 +85,6 @@ impl Embedding { } } -// impl FromSql for Embedding { -// fn column_result(value: ValueRef) -> FromSqlResult { -// let bytes = value.as_blob()?; -// let embedding: Result, Box> = bincode::deserialize(bytes); -// if embedding.is_err() { -// return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err())); -// } -// Ok(Embedding(embedding.unwrap())) -// } -// } - -// impl ToSql for Embedding { -// fn to_sql(&self) -> rusqlite::Result { -// let bytes = bincode::serialize(&self.0) -// .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?; -// Ok(ToSqlOutput::Owned(rusqlite::types::Value::Blob(bytes))) -// } -// } - #[derive(Clone)] pub struct OpenAIEmbeddings { pub client: Arc, @@ -290,7 +271,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { let mut request_number = 0; let mut rate_limiting = false; - let mut request_timeout: u64 = 30; + let mut request_timeout: u64 = 15; let mut response: Response; while request_number < MAX_RETRIES { response = self @@ -300,6 +281,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { request_timeout, ) .await?; + request_number += 1; match response.status() { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index c49d60b8ee..25c7241688 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -52,7 +52,7 @@ use std::{ }; use theme::{ components::{action_button::Button, ComponentExt}, - AssistantStyle, + AssistantStyle, Icon, }; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -2857,10 +2857,7 @@ impl View for InlineAssistant { .with_children(if self.retrieve_context { Some( Flex::row() - .with_child(Label::new( - self.retrieve_context_status(cx), - theme.assistant.inline.context_status.text.clone(), - )) + .with_children(self.retrieve_context_status(cx)) .flex(1., true) .aligned(), ) @@ -3110,40 +3107,149 @@ impl InlineAssistant { anyhow::Ok(()) } - fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { + fn retrieve_context_status( + &self, + cx: &mut ViewContext, + ) -> Option> { + enum ContextStatusIcon {} let project = self.project.clone(); - if let Some(semantic_index) = self.semantic_index.clone() { - let status = semantic_index.update(cx, |index, cx| index.status(&project)); - return match status { - // This theoretically shouldnt be a valid code path - semantic_index::SemanticIndexStatus::NotAuthenticated => { - "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() - } - semantic_index::SemanticIndexStatus::Indexed => { - "Indexing Complete!".to_string() - } - semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { + if let Some(semantic_index) = SemanticIndex::global(cx) { + let status = semantic_index.update(cx, |index, _| index.status(&project)); + let theme = theme::current(cx); + match status { + SemanticIndexStatus::NotAuthenticated {} => Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.error_icon.color) + .constrained() + .with_width(theme.assistant.error_icon.width) + .contained() + .with_style(theme.assistant.error_icon.container) + .with_tooltip::( + self.id, + "Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ), + SemanticIndexStatus::NotIndexed {} => Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.inline.context_status.error_icon.color) + .constrained() + .with_width(theme.assistant.inline.context_status.error_icon.width) + .contained() + .with_style(theme.assistant.inline.context_status.error_icon.container) + .with_tooltip::( + self.id, + "Not Indexed", + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ), + SemanticIndexStatus::Indexing { + remaining_files, + rate_limit_expiry, + } => { - let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); + let mut status_text = if remaining_files == 0 { + "Indexing...".to_string() + } else { + format!("Remaining files to index: {remaining_files}") + }; if let Some(rate_limit_expiry) = rate_limit_expiry { - let remaining_seconds = - rate_limit_expiry.duration_since(Instant::now()); - if remaining_seconds > Duration::from_secs(0) { - write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); + let remaining_seconds = rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) && remaining_files > 0 { + write!( + status_text, + " (rate limit expires in {}s)", + remaining_seconds.as_secs() + ) + .unwrap(); } } - status + Some( + Svg::new("icons/bolt.svg") + .with_color(theme.assistant.inline.context_status.in_progress_icon.color) + .constrained() + .with_width(theme.assistant.inline.context_status.in_progress_icon.width) + .contained() + .with_style(theme.assistant.inline.context_status.in_progress_icon.container) + .with_tooltip::( + self.id, + status_text, + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ) } - semantic_index::SemanticIndexStatus::NotIndexed => { - "Not Indexed for Context Retrieval".to_string() - } - }; + SemanticIndexStatus::Indexed {} => Some( + Svg::new("icons/circle_check.svg") + .with_color(theme.assistant.inline.context_status.complete_icon.color) + .constrained() + .with_width(theme.assistant.inline.context_status.complete_icon.width) + .contained() + .with_style(theme.assistant.inline.context_status.complete_icon.container) + .with_tooltip::( + self.id, + "Indexing Complete", + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ), + } + } else { + None } - - "".to_string() } + // fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { + // let project = self.project.clone(); + // if let Some(semantic_index) = self.semantic_index.clone() { + // let status = semantic_index.update(cx, |index, cx| index.status(&project)); + // return match status { + // // This theoretically shouldnt be a valid code path + // // As the inline assistant cant be launched without an API key + // // We keep it here for safety + // semantic_index::SemanticIndexStatus::NotAuthenticated => { + // "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() + // } + // semantic_index::SemanticIndexStatus::Indexed => { + // "Indexing Complete!".to_string() + // } + // semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { + + // let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); + + // if let Some(rate_limit_expiry) = rate_limit_expiry { + // let remaining_seconds = + // rate_limit_expiry.duration_since(Instant::now()); + // if remaining_seconds > Duration::from_secs(0) { + // write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); + // } + // } + // status + // } + // semantic_index::SemanticIndexStatus::NotIndexed => { + // "Not Indexed for Context Retrieval".to_string() + // } + // }; + // } + + // "".to_string() + // } + fn toggle_include_conversation( &mut self, _: &ToggleIncludeConversation, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 4ed32b6d1b..21673b0f04 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1191,7 +1191,14 @@ pub struct InlineAssistantStyle { pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, pub retrieve_context: ToggleIconButtonStyle, - pub context_status: ContainedText, + pub context_status: ContextStatusStyle, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ContextStatusStyle { + pub error_icon: Icon, + pub in_progress_icon: Icon, + pub complete_icon: Icon, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 7e7b597956..57737eab06 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -80,7 +80,21 @@ export default function assistant(): any { }, pending_edit_background: background(theme.highest, "positive"), context_status: { - ...text(theme.highest, "mono", "disabled", { size: "sm" }), + error_icon: { + margin: { left: 8, right: 8 }, + color: foreground(theme.highest, "negative"), + width: 12, + }, + in_progress_icon: { + margin: { left: 8, right: 8 }, + color: foreground(theme.highest, "warning"), + width: 12, + }, + complete_icon: { + margin: { left: 8, right: 8 }, + color: foreground(theme.highest, "positive"), + width: 12, + } }, retrieve_context: toggleable({ base: interactive({ @@ -94,6 +108,7 @@ export default function assistant(): any { border: { width: 1., color: background(theme.highest, "on") }, + margin: { left: 2 }, padding: { left: 4, right: 4, From c0a13285321754a27cb04b0ad3ff034262e515ef Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 08:30:54 +0300 Subject: [PATCH 10/17] fix spawn bug from calling --- crates/assistant/src/assistant_panel.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 25c7241688..e25514a4e4 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -3100,8 +3100,13 @@ impl InlineAssistant { project: ModelHandle, cx: &mut ViewContext, ) -> anyhow::Result<()> { - if let Some(semantic_index) = self.semantic_index.clone() { - let _ = semantic_index.update(cx, |index, cx| index.index_project(project, cx)); + if let Some(semantic_index) = SemanticIndex::global(cx) { + cx.spawn(|_, mut cx| async move { + semantic_index + .update(&mut cx, |index, cx| index.index_project(project, cx)) + .await + }) + .detach_and_log_err(cx); } anyhow::Ok(()) From 38ccf23567f134fc6e43bdfc6fecb64e6d358eb8 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 08:46:40 +0300 Subject: [PATCH 11/17] add indexing on inline assistant opening --- crates/assistant/src/assistant_panel.rs | 58 +++++++++++++++++-------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e25514a4e4..7e199a4a2f 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -24,10 +24,10 @@ use futures::StreamExt; use gpui::{ actions, elements::{ - ChildView, Component, Empty, Flex, Label, LabelStyle, MouseEventHandler, ParentElement, - SafeStylable, Stack, Svg, Text, UniformList, UniformListState, + ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable, + Stack, Svg, Text, UniformList, UniformListState, }, - fonts::{HighlightStyle, TextStyle}, + fonts::HighlightStyle, geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, @@ -52,7 +52,7 @@ use std::{ }; use theme::{ components::{action_button::Button, ComponentExt}, - AssistantStyle, Icon, + AssistantStyle, }; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -2755,7 +2755,7 @@ struct InlineAssistant { retrieve_context: bool, semantic_index: Option>, semantic_permissioned: Option, - project: ModelHandle, + project: WeakModelHandle, maintain_rate_limit: Option>, } @@ -2914,7 +2914,7 @@ impl InlineAssistant { subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed)); } - Self { + let assistant = Self { id, prompt_editor, workspace, @@ -2930,9 +2930,13 @@ impl InlineAssistant { retrieve_context, semantic_permissioned: None, semantic_index, - project, + project: project.downgrade(), maintain_rate_limit: None, - } + }; + + assistant.index_project(cx).log_err(); + + assistant } fn semantic_permissioned(&mut self, cx: &mut ViewContext) -> Task> { @@ -2940,7 +2944,10 @@ impl InlineAssistant { return Task::ready(Ok(value)); } - let project = self.project.clone(); + let Some(project) = self.project.upgrade(cx) else { + return Task::ready(Err(anyhow!("project was dropped"))); + }; + self.semantic_index .as_mut() .map(|semantic| { @@ -2966,7 +2973,10 @@ impl InlineAssistant { semantic_index: ModelHandle, cx: &mut ViewContext, ) { - let project = self.project.clone(); + let Some(project) = self.project.upgrade(cx) else { + return; + }; + let status = semantic_index.read(cx).status(&project); match status { SemanticIndexStatus::Indexing { @@ -3047,7 +3057,11 @@ impl InlineAssistant { fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext) { let semantic_permissioned = self.semantic_permissioned(cx); - let project = self.project.clone(); + + let Some(project) = self.project.upgrade(cx) else { + return; + }; + let project_name = project .read(cx) .worktree_root_names(cx) @@ -3086,7 +3100,11 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); - this.index_project(project, cx).log_err(); + + if this.retrieve_context { + this.index_project(cx).log_err(); + } + cx.notify(); })?; @@ -3095,11 +3113,11 @@ impl InlineAssistant { .detach_and_log_err(cx); } - fn index_project( - &self, - project: ModelHandle, - cx: &mut ViewContext, - ) -> anyhow::Result<()> { + fn index_project(&self, cx: &mut ViewContext) -> anyhow::Result<()> { + let Some(project) = self.project.upgrade(cx) else { + return Err(anyhow!("project was dropped!")); + }; + if let Some(semantic_index) = SemanticIndex::global(cx) { cx.spawn(|_, mut cx| async move { semantic_index @@ -3117,7 +3135,11 @@ impl InlineAssistant { cx: &mut ViewContext, ) -> Option> { enum ContextStatusIcon {} - let project = self.project.clone(); + + let Some(project) = self.project.upgrade(cx) else { + return None; + }; + if let Some(semantic_index) = SemanticIndex::global(cx) { let status = semantic_index.update(cx, |index, _| index.status(&project)); let theme = theme::current(cx); From 84553899f6d3cb5f423857fd301cb53c46c2dfb9 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 15:43:28 +0200 Subject: [PATCH 12/17] updated spacing for assistant context status icon --- styles/src/style_tree/assistant.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 57737eab06..08297731bb 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -81,17 +81,17 @@ export default function assistant(): any { pending_edit_background: background(theme.highest, "positive"), context_status: { error_icon: { - margin: { left: 8, right: 8 }, + margin: { left: 8, right: 18 }, color: foreground(theme.highest, "negative"), width: 12, }, in_progress_icon: { - margin: { left: 8, right: 8 }, - color: foreground(theme.highest, "warning"), + margin: { left: 8, right: 18 }, + color: foreground(theme.highest, "positive"), width: 12, }, complete_icon: { - margin: { left: 8, right: 8 }, + margin: { left: 8, right: 18 }, color: foreground(theme.highest, "positive"), width: 12, } From ed548a0de223d03dcb1067309be060e556f1ca55 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 16:08:36 +0200 Subject: [PATCH 13/17] ensure indexing is only done when permissioned --- crates/assistant/src/assistant_panel.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7e199a4a2f..17e5c161c7 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2939,7 +2939,7 @@ impl InlineAssistant { assistant } - fn semantic_permissioned(&mut self, cx: &mut ViewContext) -> Task> { + fn semantic_permissioned(&self, cx: &mut ViewContext) -> Task> { if let Some(value) = self.semantic_permissioned { return Task::ready(Ok(value)); } @@ -2949,7 +2949,7 @@ impl InlineAssistant { }; self.semantic_index - .as_mut() + .as_ref() .map(|semantic| { semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx)) }) @@ -3118,11 +3118,17 @@ impl InlineAssistant { return Err(anyhow!("project was dropped!")); }; + let semantic_permissioned = self.semantic_permissioned(cx); if let Some(semantic_index) = SemanticIndex::global(cx) { cx.spawn(|_, mut cx| async move { - semantic_index - .update(&mut cx, |index, cx| index.index_project(project, cx)) - .await + // This has to be updated to accomodate for semantic_permissions + if semantic_permissioned.await.unwrap_or(false) { + semantic_index + .update(&mut cx, |index, cx| index.index_project(project, cx)) + .await + } else { + Err(anyhow!("project is not permissioned for semantic indexing")) + } }) .detach_and_log_err(cx); } From 391179657cdd30f2d4f851c6c945b39fa9b6b9da Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 16:43:19 +0200 Subject: [PATCH 14/17] clean up redundancies in prompts and ensure tokens are being reserved for generation when filling semantic context --- crates/assistant/src/prompts.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 487950dbef..a3a2be1a00 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -125,6 +125,7 @@ pub fn generate_content_prompt( model: &str, ) -> String { const MAXIMUM_SNIPPET_TOKEN_COUNT: usize = 500; + const RESERVED_TOKENS_FOR_GENERATION: usize = 1000; let mut prompts = Vec::new(); @@ -182,11 +183,17 @@ pub fn generate_content_prompt( name: None, }]; - let remaining_token_count = if let Ok(current_token_count) = + let mut remaining_token_count = if let Ok(current_token_count) = tiktoken_rs::num_tokens_from_messages(model, ¤t_messages) { let max_token_count = tiktoken_rs::model::get_context_size(model); - max_token_count - current_token_count + let intermediate_token_count = max_token_count - current_token_count; + + if intermediate_token_count < RESERVED_TOKENS_FOR_GENERATION { + 0 + } else { + intermediate_token_count - RESERVED_TOKENS_FOR_GENERATION + } } else { // If tiktoken fails to count token count, assume we have no space remaining. 0 @@ -197,7 +204,7 @@ pub fn generate_content_prompt( // - add file path // - add language if let Ok(encoding) = tiktoken_rs::get_bpe_from_model(model) { - let template = "You are working inside a large repository, here are a few code snippets that may be useful"; + let mut template = "You are working inside a large repository, here are a few code snippets that may be useful"; for search_result in search_results { let mut snippet_prompt = template.to_string(); @@ -210,6 +217,9 @@ pub fn generate_content_prompt( if token_count < MAXIMUM_SNIPPET_TOKEN_COUNT { prompts.insert(snippet_position, snippet_prompt); snippet_position += 1; + remaining_token_count -= token_count; + // If you have already added the template to the prompt, remove the template. + template = ""; } } else { break; From 5e1e0b475936872077126419b29418c0e51231ff Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 16 Oct 2023 09:55:45 -0400 Subject: [PATCH 15/17] remove print from prompts --- crates/assistant/src/prompts.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 3550c4223c..2fdca046ad 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -245,8 +245,8 @@ pub fn generate_content_prompt( )); } prompts.push("Never make remarks about the output.".to_string()); - prompts.push("DO NOT return any text, except the generated code.".to_string()); - prompts.push("DO NOT wrap your text in a Markdown block".to_string()); + prompts.push("Do not return any text, except the generated code.".to_string()); + prompts.push("Do not wrap your text in a Markdown block".to_string()); let current_messages = [ChatCompletionRequestMessage { role: "user".to_string(), @@ -300,9 +300,7 @@ pub fn generate_content_prompt( } } - let prompt = prompts.join("\n"); - println!("PROMPT: {:?}", prompt); - prompt + prompts.join("\n") } #[cfg(test)] From 29f45a2e384e4eaf5d43e099f4d75c4a84e4adb4 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 16 Oct 2023 10:02:11 -0400 Subject: [PATCH 16/17] clean up warnings --- crates/assistant/src/prompts.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 2fdca046ad..7aafe75920 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -1,13 +1,11 @@ use crate::codegen::CodegenKind; -use gpui::{AppContext, AsyncAppContext}; -use language::{BufferSnapshot, Language, OffsetRangeExt, ToOffset}; +use gpui::AsyncAppContext; +use language::{BufferSnapshot, OffsetRangeExt, ToOffset}; use semantic_index::SearchResult; -use std::borrow::Cow; use std::cmp::{self, Reverse}; use std::fmt::Write; use std::ops::Range; use std::path::PathBuf; -use std::sync::Arc; use tiktoken_rs::ChatCompletionRequestMessage; pub struct PromptCodeSnippet { @@ -19,7 +17,7 @@ pub struct PromptCodeSnippet { impl PromptCodeSnippet { pub fn new(search_result: SearchResult, cx: &AsyncAppContext) -> Self { let (content, language_name, file_path) = - search_result.buffer.read_with(cx, |buffer, cx| { + search_result.buffer.read_with(cx, |buffer, _| { let snapshot = buffer.snapshot(); let content = snapshot .text_for_range(search_result.range.clone()) @@ -29,7 +27,6 @@ impl PromptCodeSnippet { .language() .and_then(|language| Some(language.name().to_string())); - let language = buffer.language(); let file_path = buffer .file() .and_then(|file| Some(file.path().to_path_buf())); From 247728b723d752ed1b2e00dcbd79f8bf8bb356c2 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 16 Oct 2023 15:53:29 -0400 Subject: [PATCH 17/17] Update indexing icon Co-Authored-By: Kyle Caverly <22121886+KCaverly@users.noreply.github.com> --- assets/icons/update.svg | 8 ++++++++ crates/assistant/src/assistant_panel.rs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 assets/icons/update.svg diff --git a/assets/icons/update.svg b/assets/icons/update.svg new file mode 100644 index 0000000000..b529b2b08b --- /dev/null +++ b/assets/icons/update.svg @@ -0,0 +1,8 @@ + + + diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e8edf70498..65edb1832f 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -3223,7 +3223,7 @@ impl InlineAssistant { } } Some( - Svg::new("icons/bolt.svg") + Svg::new("icons/update.svg") .with_color(theme.assistant.inline.context_status.in_progress_icon.color) .constrained() .with_width(theme.assistant.inline.context_status.in_progress_icon.width) @@ -3241,7 +3241,7 @@ impl InlineAssistant { ) } SemanticIndexStatus::Indexed {} => Some( - Svg::new("icons/circle_check.svg") + Svg::new("icons/check.svg") .with_color(theme.assistant.inline.context_status.complete_icon.color) .constrained() .with_width(theme.assistant.inline.context_status.complete_icon.width) @@ -3249,7 +3249,7 @@ impl InlineAssistant { .with_style(theme.assistant.inline.context_status.complete_icon.container) .with_tooltip::( self.id, - "Indexing Complete", + "Index up to date", None, theme.tooltip.clone(), cx,