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 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 {
|
||||||
|
|
|
@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue