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