This commit is contained in:
Antonio Scandurra 2023-12-05 18:14:24 +01:00
parent ede86d9187
commit e534c5fdcd
2 changed files with 166 additions and 337 deletions

View file

@ -27,9 +27,10 @@ use editor::{
use fs::Fs; use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, point, uniform_list, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, actions, div, point, uniform_list, Action, AnyElement, AppContext, AsyncAppContext,
Div, Element, Entity, EventEmitter, FocusHandle, FocusableView, HighlightStyle, ClipboardItem, Div, Element, Entity, EventEmitter, FocusHandle, Focusable, FocusableView,
InteractiveElement, IntoElement, Model, ModelContext, Render, Styled, Subscription, Task, HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels,
PromptLevel, Render, StatefulInteractiveElement, Styled, Subscription, Task,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
}; };
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
@ -48,7 +49,10 @@ use std::{
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use ui::{h_stack, v_stack, ButtonCommon, ButtonLike, Clickable, IconButton, Label}; use ui::{
h_stack, v_stack, Button, ButtonCommon, ButtonLike, Clickable, Color, Icon, IconButton,
IconElement, Label, Selectable, StyledExt, Tooltip,
};
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid; use uuid::Uuid;
use workspace::{ use workspace::{
@ -958,7 +962,7 @@ impl AssistantPanel {
} }
fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
IconButton::new("hamburger_button", ui::Icon::Menu) IconButton::new("hamburger_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
if this.active_editor().is_some() { if this.active_editor().is_some() {
this.set_active_editor_index(None, cx); this.set_active_editor_index(None, cx);
@ -966,7 +970,7 @@ impl AssistantPanel {
this.set_active_editor_index(this.prev_active_editor_index, cx); this.set_active_editor_index(this.prev_active_editor_index, cx);
} }
})) }))
.tooltip(|cx| ui::Tooltip::text("History", cx)) .tooltip(|cx| Tooltip::text("History", cx))
} }
fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> { fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
@ -982,27 +986,27 @@ impl AssistantPanel {
} }
fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
IconButton::new("split_button", ui::Icon::Menu) IconButton::new("split_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
if let Some(active_editor) = this.active_editor() { if let Some(active_editor) = this.active_editor() {
active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx)); active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
} }
})) }))
.tooltip(|cx| ui::Tooltip::for_action("Split Message", &Split, cx)) .tooltip(|cx| Tooltip::for_action("Split Message", &Split, cx))
} }
fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
IconButton::new("assist_button", ui::Icon::Menu) IconButton::new("assist_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
if let Some(active_editor) = this.active_editor() { if let Some(active_editor) = this.active_editor() {
active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx)); active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
} }
})) }))
.tooltip(|cx| ui::Tooltip::for_action("Assist", &Assist, cx)) .tooltip(|cx| Tooltip::for_action("Assist", &Assist, cx))
} }
fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
IconButton::new("quote_button", ui::Icon::Menu) IconButton::new("quote_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
if let Some(workspace) = this.workspace.upgrade() { if let Some(workspace) = this.workspace.upgrade() {
cx.window_context().defer(move |cx| { cx.window_context().defer(move |cx| {
@ -1012,24 +1016,24 @@ impl AssistantPanel {
}); });
} }
})) }))
.tooltip(|cx| ui::Tooltip::for_action("Quote Seleciton", &QuoteSelection, cx)) .tooltip(|cx| Tooltip::for_action("Quote Seleciton", &QuoteSelection, cx))
} }
fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
IconButton::new("plus_button", ui::Icon::Menu) IconButton::new("plus_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
this.new_conversation(cx); this.new_conversation(cx);
})) }))
.tooltip(|cx| ui::Tooltip::for_action("New Conversation", &NewConversation, cx)) .tooltip(|cx| Tooltip::for_action("New Conversation", &NewConversation, cx))
} }
fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
IconButton::new("zoom_button", ui::Icon::Menu) IconButton::new("zoom_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
this.toggle_zoom(&ToggleZoom, cx); this.toggle_zoom(&ToggleZoom, cx);
})) }))
.tooltip(|cx| { .tooltip(|cx| {
ui::Tooltip::for_action( Tooltip::for_action(
if self.zoomed { "Zoom Out" } else { "Zoom In" }, if self.zoomed { "Zoom Out" } else { "Zoom In" },
&ToggleZoom, &ToggleZoom,
cx, cx,
@ -1111,9 +1115,9 @@ fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
} }
impl Render for AssistantPanel { impl Render for AssistantPanel {
type Element = Div; type Element = Focusable<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
if let Some(api_key_editor) = self.api_key_editor.clone() { if let Some(api_key_editor) = self.api_key_editor.clone() {
v_stack() v_stack()
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
@ -1249,8 +1253,8 @@ impl Panel for AssistantPanel {
} }
} }
fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> { fn icon(&self, cx: &WindowContext) -> Option<Icon> {
Some(ui::Icon::Ai) Some(Icon::Ai)
} }
fn toggle_action(&self) -> Box<dyn Action> { fn toggle_action(&self) -> Box<dyn Action> {
@ -2052,6 +2056,7 @@ struct ConversationEditor {
editor: View<Editor>, editor: View<Editor>,
blocks: HashSet<BlockId>, blocks: HashSet<BlockId>,
scroll_position: Option<ScrollPosition>, scroll_position: Option<ScrollPosition>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -2082,10 +2087,13 @@ impl ConversationEditor {
editor editor
}); });
let focus_handle = cx.focus_handle();
let _subscriptions = vec![ let _subscriptions = vec![
cx.observe(&conversation, |_, _, cx| cx.notify()), cx.observe(&conversation, |_, _, cx| cx.notify()),
cx.subscribe(&conversation, Self::handle_conversation_event), cx.subscribe(&conversation, Self::handle_conversation_event),
cx.subscribe(&editor, Self::handle_editor_event), cx.subscribe(&editor, Self::handle_editor_event),
cx.on_focus(&focus_handle, |this, _, cx| cx.focus(&this.editor)),
]; ];
let mut this = Self { let mut this = Self {
@ -2095,6 +2103,7 @@ impl ConversationEditor {
scroll_position: None, scroll_position: None,
fs, fs,
workspace, workspace,
focus_handle,
_subscriptions, _subscriptions,
}; };
this.update_message_headers(cx); this.update_message_headers(cx);
@ -2265,39 +2274,17 @@ impl ConversationEditor {
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
render: Arc::new({ render: Arc::new({
let conversation = self.conversation.clone(); let conversation = self.conversation.clone();
// let metadata = message.metadata.clone();
// let message = message.clone();
move |cx| { move |cx| {
enum Sender {}
enum ErrorTooltip {}
let message_id = message.id; let message_id = message.id;
let sender = MouseEventHandler::new::<Sender, _>( let sender = ButtonLike::new("role")
message_id.0, .child(match message.role {
cx, Role::User => Label::new("You").color(Color::Default),
|state, _| match message.role {
Role::User => {
let style = style.user_sender.style_for(state);
Label::new("You", style.text.clone())
.contained()
.with_style(style.container)
}
Role::Assistant => { Role::Assistant => {
let style = style.assistant_sender.style_for(state); Label::new("Assistant").color(Color::Modified)
Label::new("Assistant", style.text.clone())
.contained()
.with_style(style.container)
} }
Role::System => { Role::System => Label::new("System").color(Color::Warning),
let style = style.system_sender.style_for(state); })
Label::new("System", style.text.clone()) .on_click({
.contained()
.with_style(style.container)
}
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_down(MouseButton::Left, {
let conversation = conversation.clone(); let conversation = conversation.clone();
move |_, _, cx| { move |_, _, cx| {
conversation.update(cx, |conversation, cx| { conversation.update(cx, |conversation, cx| {
@ -2309,44 +2296,25 @@ impl ConversationEditor {
} }
}); });
Flex::row() h_stack()
.with_child(sender.aligned()) .id(("message_header", message_id.0))
.with_child( .border()
Label::new( .border_color(gpui::red())
message.sent_at.format("%I:%M%P").to_string(), .child(sender)
style.sent_at.text.clone(), .child(Label::new(message.sent_at.format("%I:%M%P").to_string()))
)
.contained()
.with_style(style.sent_at.container)
.aligned(),
)
.with_children( .with_children(
if let MessageStatus::Error(error) = &message.status { if let MessageStatus::Error(error) = &message.status {
Some( Some(
Svg::new("icons/error.svg") div()
.with_color(style.error_icon.color) .id("error")
.constrained() .tooltip(|cx| Tooltip::text(error, cx))
.with_width(style.error_icon.width) .child(IconElement::new(Icon::XCircle)),
.contained()
.with_style(style.error_icon.container)
.with_tooltip::<ErrorTooltip>(
message_id.0,
error.to_string(),
None,
theme.tooltip.clone(),
cx,
)
.aligned(),
) )
} else { } else {
None None
}, },
) )
.aligned() .into_any_element()
.left()
.contained()
.with_style(style.message_header)
.into_any()
} }
}), }),
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
@ -2491,78 +2459,48 @@ impl ConversationEditor {
.unwrap_or_else(|| "New Conversation".into()) .unwrap_or_else(|| "New Conversation".into())
} }
fn render_current_model( fn render_current_model(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
&self, Button::new(
style: &AssistantStyle, "current_model",
cx: &mut ViewContext<Self>, self.conversation.read(cx).model.short_name(),
) -> impl Element<Self> { )
enum Model {} .tooltip(move |cx| Tooltip::text("Change Model", cx))
.on_click(cx.listener(|this, _, cx| this.cycle_model(cx)))
MouseEventHandler::new::<Model, _>(0, cx, |state, cx| {
let style = style.model.style_for(state);
let model_display_name = self.conversation.read(cx).model.short_name();
Label::new(model_display_name, style.text.clone())
.contained()
.with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, this, cx| this.cycle_model(cx))
} }
fn render_remaining_tokens( fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
&self,
style: &AssistantStyle,
cx: &mut ViewContext<Self>,
) -> Option<impl Element<Self>> {
let remaining_tokens = self.conversation.read(cx).remaining_tokens()?; let remaining_tokens = self.conversation.read(cx).remaining_tokens()?;
let remaining_tokens_style = if remaining_tokens <= 0 { let remaining_tokens_color = if remaining_tokens <= 0 {
&style.no_remaining_tokens Color::Error
} else if remaining_tokens <= 500 { } else if remaining_tokens <= 500 {
&style.low_remaining_tokens Color::Warning
} else { } else {
&style.remaining_tokens Color::Default
}; };
Some( Some(
Label::new( div()
remaining_tokens.to_string(), .border()
remaining_tokens_style.text.clone(), .border_color(gpui::red())
) .child(Label::new(remaining_tokens.to_string()).color(remaining_tokens_color)),
.contained()
.with_style(remaining_tokens_style.container),
) )
} }
} }
impl EventEmitter<ConversationEditorEvent> for ConversationEditor {} impl EventEmitter<ConversationEditorEvent> for ConversationEditor {}
impl View for ConversationEditor { impl Render for ConversationEditor {
fn ui_name() -> &'static str { type Element = Div;
"ConversationEditor"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let theme = &theme::current(cx).assistant; div().relative().child(self.editor.clone()).child(
Stack::new() h_stack()
.with_child( .absolute()
ChildView::new(&self.editor, cx) .gap_1()
.contained() .top_3()
.with_style(theme.container), .right_5()
.child(self.render_current_model(cx))
.children(self.render_remaining_tokens(cx)),
) )
.with_child(
Flex::row()
.with_child(self.render_current_model(theme, cx))
.with_children(self.render_remaining_tokens(theme, cx))
.aligned()
.top()
.right(),
)
.into_any()
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
cx.focus(&self.editor);
}
} }
} }
@ -2616,7 +2554,7 @@ struct InlineAssistant {
prompt_editor: View<Editor>, prompt_editor: View<Editor>,
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
confirmed: bool, confirmed: bool,
has_focus: bool, focus_handle: FocusHandle,
include_conversation: bool, include_conversation: bool,
measurements: Rc<Cell<BlockMeasurements>>, measurements: Rc<Cell<BlockMeasurements>>,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
@ -2631,124 +2569,63 @@ struct InlineAssistant {
maintain_rate_limit: Option<Task<()>>, maintain_rate_limit: Option<Task<()>>,
} }
impl Entity for InlineAssistant { impl EventEmitter<InlineAssistantEvent> for InlineAssistant {}
type Event = InlineAssistantEvent;
}
impl View for InlineAssistant { impl Render for InlineAssistant {
fn ui_name() -> &'static str { type Element = Div;
"InlineAssistant"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
enum ErrorIcon {} let measurements = self.measurements.get();
let theme = theme::current(cx); h_stack()
.child(
Flex::row() h_stack()
.with_children([Flex::row() .justify_center()
.with_child( .w(measurements.gutter_width)
Button::action(ToggleIncludeConversation) .child(
.with_tooltip("Include Conversation", theme.tooltip.clone()) IconButton::new("include_conversation", Icon::Ai)
.with_id(self.id) .action(ToggleIncludeConversation)
.with_contents(theme::components::svg::Svg::new("icons/ai.svg")) .selected(self.include_conversation)
.toggleable(self.include_conversation) .tooltip(Tooltip::for_action(
.with_style(theme.assistant.inline.include_conversation.clone()) "Include Conversation",
.element() &ToggleIncludeConversation,
.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::<ErrorIcon>(
self.id,
error.to_string(),
None,
theme.tooltip.clone(),
cx, cx,
)),
) )
.aligned(), .children(if SemanticIndex::enabled(cx) {
)
} 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, _, _| {
let measurements = measurements.get();
SizeConstraint {
min: vec2f(
measurements.anchor_x - measurements.gutter_width,
constraint.min.y(),
),
max: vec2f(
measurements.anchor_x - measurements.gutter_width,
constraint.max.y(),
),
}
}
}))
.with_child(
ChildView::new(&self.prompt_editor, cx)
.aligned()
.left()
.flex(1., true),
)
.with_children(if self.retrieve_context {
Some( Some(
Flex::row() IconButton::new("retrieve_context", Icon::MagnifyingGlass)
.with_children(self.retrieve_context_status(cx)) .action(ToggleRetrieveContext)
.flex(1., true) .selected(self.retrieve_context)
.aligned(), .tooltip(Tooltip::for_action(
"Retrieve Context",
&ToggleRetrieveContext,
cx,
)),
) )
} else { } else {
None None
}) })
.contained() .children(if let Some(error) = self.codegen.read(cx).error() {
.with_style(theme.assistant.inline.container) Some(
.into_any() div()
.into_any() .id("error")
} .tooltip(|cx| Tooltip::text(error.to_string(), cx))
.child(IconElement::new(Icon::XCircle).color(Color::Error)),
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) { )
cx.focus(&self.prompt_editor); } else {
self.has_focus = true; None
} }),
)
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) { .child(
self.has_focus = false; div()
.ml(measurements.anchor_x - measurements.gutter_width)
.child(self.prompt_editor.clone()),
)
.children(if self.retrieve_context {
self.retrieve_context_status(cx)
} else {
None
})
} }
} }
@ -2765,11 +2642,8 @@ impl InlineAssistant {
semantic_index: Option<Model<SemanticIndex>>, semantic_index: Option<Model<SemanticIndex>>,
project: Model<Project>, project: Model<Project>,
) -> Self { ) -> Self {
let prompt_editor = cx.add_view(|cx| { let prompt_editor = cx.build_view(|cx| {
let mut editor = Editor::single_line( let mut editor = Editor::single_line(cx);
Some(Arc::new(|theme| theme.assistant.inline.editor.clone())),
cx,
);
let placeholder = match codegen.read(cx).kind() { let placeholder = match codegen.read(cx).kind() {
CodegenKind::Transform { .. } => "Enter transformation prompt…", CodegenKind::Transform { .. } => "Enter transformation prompt…",
CodegenKind::Generate { .. } => "Enter generation prompt…", CodegenKind::Generate { .. } => "Enter generation prompt…",
@ -2777,9 +2651,15 @@ impl InlineAssistant {
editor.set_placeholder_text(placeholder, cx); editor.set_placeholder_text(placeholder, cx);
editor editor
}); });
let focus_handle = cx.focus_handle();
let mut subscriptions = vec![ let mut subscriptions = vec![
cx.observe(&codegen, Self::handle_codegen_changed), cx.observe(&codegen, Self::handle_codegen_changed),
cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
cx.on_focus(
&focus_handle,
cx.listener(|this, _, cx| cx.focus(&this.prompt_editor)),
),
]; ];
if let Some(semantic_index) = semantic_index.clone() { if let Some(semantic_index) = semantic_index.clone() {
@ -2791,7 +2671,7 @@ impl InlineAssistant {
prompt_editor, prompt_editor,
workspace, workspace,
confirmed: false, confirmed: false,
has_focus: false, focus_handle,
include_conversation, include_conversation,
measurements, measurements,
prompt_history, prompt_history,
@ -3008,10 +2888,7 @@ impl InlineAssistant {
anyhow::Ok(()) anyhow::Ok(())
} }
fn retrieve_context_status( fn retrieve_context_status(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
&self,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement<InlineAssistant>> {
enum ContextStatusIcon {} enum ContextStatusIcon {}
let Some(project) = self.project.upgrade() else { let Some(project) = self.project.upgrade() else {
@ -3020,47 +2897,27 @@ impl InlineAssistant {
if let Some(semantic_index) = SemanticIndex::global(cx) { if let Some(semantic_index) = SemanticIndex::global(cx) {
let status = semantic_index.update(cx, |index, _| index.status(&project)); let status = semantic_index.update(cx, |index, _| index.status(&project));
let theme = theme::current(cx);
match status { match status {
SemanticIndexStatus::NotAuthenticated {} => Some( SemanticIndexStatus::NotAuthenticated {} => Some(
Svg::new("icons/error.svg") div()
.with_color(theme.assistant.error_icon.color) .id("error")
.constrained() .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx))
.with_width(theme.assistant.error_icon.width) .child(IconElement::new(Icon::XCircle))
.contained() .into_any_element()
.with_style(theme.assistant.error_icon.container)
.with_tooltip::<ContextStatusIcon>(
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( SemanticIndexStatus::NotIndexed {} => Some(
Svg::new("icons/error.svg") div()
.with_color(theme.assistant.inline.context_status.error_icon.color) .id("error")
.constrained() .tooltip(|cx| Tooltip::text("Not Indexed", cx))
.with_width(theme.assistant.inline.context_status.error_icon.width) .child(IconElement::new(Icon::XCircle))
.contained() .into_any_element()
.with_style(theme.assistant.inline.context_status.error_icon.container)
.with_tooltip::<ContextStatusIcon>(
self.id,
"Not Indexed",
None,
theme.tooltip.clone(),
cx,
)
.aligned()
.into_any(),
), ),
SemanticIndexStatus::Indexing { SemanticIndexStatus::Indexing {
remaining_files, remaining_files,
rate_limit_expiry, rate_limit_expiry,
} => { } => {
let mut status_text = if remaining_files == 0 { let mut status_text = if remaining_files == 0 {
"Indexing...".to_string() "Indexing...".to_string()
} else { } else {
@ -3079,6 +2936,11 @@ impl InlineAssistant {
} }
} }
Some( Some(
div()
.id("update")
.tooltip(|cx| Tooltip::text(status_text, cx))
.child(IconElement::new(Icon::Update).color(color))
.into_any_element()
Svg::new("icons/update.svg") Svg::new("icons/update.svg")
.with_color(theme.assistant.inline.context_status.in_progress_icon.color) .with_color(theme.assistant.inline.context_status.in_progress_icon.color)
.constrained() .constrained()
@ -3096,6 +2958,7 @@ impl InlineAssistant {
.into_any(), .into_any(),
) )
} }
SemanticIndexStatus::Indexed {} => Some( SemanticIndexStatus::Indexed {} => Some(
Svg::new("icons/check.svg") Svg::new("icons/check.svg")
.with_color(theme.assistant.inline.context_status.complete_icon.color) .with_color(theme.assistant.inline.context_status.complete_icon.color)
@ -3119,42 +2982,6 @@ impl InlineAssistant {
} }
} }
// fn retrieve_context_status(&self, cx: &mut ViewContext<Self>) -> 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( fn toggle_include_conversation(
&mut self, &mut self,
_: &ToggleIncludeConversation, _: &ToggleIncludeConversation,
@ -3208,8 +3035,8 @@ impl InlineAssistant {
// This wouldn't need to exist if we could pass parameters when rendering child views. // This wouldn't need to exist if we could pass parameters when rendering child views.
#[derive(Copy, Clone, Default)] #[derive(Copy, Clone, Default)]
struct BlockMeasurements { struct BlockMeasurements {
anchor_x: f32, anchor_x: Pixels,
gutter_width: f32, gutter_width: Pixels,
} }
struct PendingInlineAssist { struct PendingInlineAssist {

View file

@ -81,6 +81,7 @@ pub enum Icon {
Shift, Shift,
Option, Option,
Return, Return,
Update,
} }
impl Icon { impl Icon {
@ -155,6 +156,7 @@ impl Icon {
Icon::Shift => "icons/shift.svg", Icon::Shift => "icons/shift.svg",
Icon::Option => "icons/option.svg", Icon::Option => "icons/option.svg",
Icon::Return => "icons/return.svg", Icon::Return => "icons/return.svg",
Icon::Update => "icons/update.svg",
} }
} }
} }