Allow including conversation when triggering inline assist
This commit is contained in:
parent
ccec59337a
commit
c2b60df5af
3 changed files with 189 additions and 26 deletions
|
@ -19,12 +19,16 @@ use fs::Fs;
|
||||||
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
elements::*,
|
elements::{
|
||||||
|
ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable,
|
||||||
|
Stack, Svg, Text, UniformList, UniformListState,
|
||||||
|
},
|
||||||
fonts::HighlightStyle,
|
fonts::HighlightStyle,
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::{vec2f, Vector2F},
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
Action, AppContext, AsyncAppContext, ClipboardItem, Entity, ModelContext, ModelHandle,
|
Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext,
|
||||||
Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::SoftWrap, Buffer, LanguageRegistry, Point, Rope, ToOffset as _,
|
language_settings::SoftWrap, Buffer, LanguageRegistry, Point, Rope, ToOffset as _,
|
||||||
|
@ -33,7 +37,7 @@ use language::{
|
||||||
use search::BufferSearchBar;
|
use search::BufferSearchBar;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::{Cell, RefCell},
|
||||||
cmp, env,
|
cmp, env,
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
iter,
|
iter,
|
||||||
|
@ -43,7 +47,10 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use theme::AssistantStyle;
|
use theme::{
|
||||||
|
components::{action_button::Button, ComponentExt},
|
||||||
|
AssistantStyle,
|
||||||
|
};
|
||||||
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
|
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel},
|
dock::{DockPosition, Panel},
|
||||||
|
@ -61,7 +68,8 @@ actions!(
|
||||||
QuoteSelection,
|
QuoteSelection,
|
||||||
ToggleFocus,
|
ToggleFocus,
|
||||||
ResetKey,
|
ResetKey,
|
||||||
InlineAssist
|
InlineAssist,
|
||||||
|
ToggleIncludeConversation,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -97,6 +105,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(AssistantPanel::cancel_last_inline_assist);
|
cx.add_action(AssistantPanel::cancel_last_inline_assist);
|
||||||
cx.add_action(InlineAssistant::confirm);
|
cx.add_action(InlineAssistant::confirm);
|
||||||
cx.add_action(InlineAssistant::cancel);
|
cx.add_action(InlineAssistant::cancel);
|
||||||
|
cx.add_action(InlineAssistant::toggle_include_conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -129,6 +138,7 @@ pub struct AssistantPanel {
|
||||||
next_inline_assist_id: usize,
|
next_inline_assist_id: usize,
|
||||||
pending_inline_assists: HashMap<usize, PendingInlineAssist>,
|
pending_inline_assists: HashMap<usize, PendingInlineAssist>,
|
||||||
pending_inline_assist_ids_by_editor: HashMap<WeakViewHandle<Editor>, Vec<usize>>,
|
pending_inline_assist_ids_by_editor: HashMap<WeakViewHandle<Editor>, Vec<usize>>,
|
||||||
|
include_conversation_in_next_inline_assist: bool,
|
||||||
_watch_saved_conversations: Task<Result<()>>,
|
_watch_saved_conversations: Task<Result<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +205,7 @@ impl AssistantPanel {
|
||||||
next_inline_assist_id: 0,
|
next_inline_assist_id: 0,
|
||||||
pending_inline_assists: Default::default(),
|
pending_inline_assists: Default::default(),
|
||||||
pending_inline_assist_ids_by_editor: Default::default(),
|
pending_inline_assist_ids_by_editor: Default::default(),
|
||||||
|
include_conversation_in_next_inline_assist: false,
|
||||||
_watch_saved_conversations,
|
_watch_saved_conversations,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,12 +281,15 @@ impl AssistantPanel {
|
||||||
editor.set_placeholder_text(placeholder, cx);
|
editor.set_placeholder_text(placeholder, cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
|
||||||
let inline_assistant = cx.add_view(|cx| {
|
let inline_assistant = cx.add_view(|cx| {
|
||||||
let assistant = InlineAssistant {
|
let assistant = InlineAssistant {
|
||||||
id: inline_assist_id,
|
id: inline_assist_id,
|
||||||
prompt_editor,
|
prompt_editor,
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
has_focus: false,
|
has_focus: false,
|
||||||
|
include_conversation: self.include_conversation_in_next_inline_assist,
|
||||||
|
measurements: measurements.clone(),
|
||||||
};
|
};
|
||||||
cx.focus_self();
|
cx.focus_self();
|
||||||
assistant
|
assistant
|
||||||
|
@ -292,13 +306,11 @@ impl AssistantPanel {
|
||||||
render: Arc::new({
|
render: Arc::new({
|
||||||
let inline_assistant = inline_assistant.clone();
|
let inline_assistant = inline_assistant.clone();
|
||||||
move |cx: &mut BlockContext| {
|
move |cx: &mut BlockContext| {
|
||||||
let theme = theme::current(cx);
|
measurements.set(BlockMeasurements {
|
||||||
ChildView::new(&inline_assistant, cx)
|
anchor_x: cx.anchor_x,
|
||||||
.contained()
|
gutter_width: cx.gutter_width,
|
||||||
.with_padding_left(cx.anchor_x)
|
});
|
||||||
.contained()
|
ChildView::new(&inline_assistant, cx).into_any()
|
||||||
.with_style(theme.assistant.inline.container)
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
disposition: if selection.reversed {
|
disposition: if selection.reversed {
|
||||||
|
@ -375,8 +387,11 @@ impl AssistantPanel {
|
||||||
) {
|
) {
|
||||||
let assist_id = inline_assistant.read(cx).id;
|
let assist_id = inline_assistant.read(cx).id;
|
||||||
match event {
|
match event {
|
||||||
InlineAssistantEvent::Confirmed { prompt } => {
|
InlineAssistantEvent::Confirmed {
|
||||||
self.confirm_inline_assist(assist_id, prompt, cx);
|
prompt,
|
||||||
|
include_conversation,
|
||||||
|
} => {
|
||||||
|
self.confirm_inline_assist(assist_id, prompt, *include_conversation, cx);
|
||||||
}
|
}
|
||||||
InlineAssistantEvent::Canceled => {
|
InlineAssistantEvent::Canceled => {
|
||||||
self.close_inline_assist(assist_id, true, cx);
|
self.close_inline_assist(assist_id, true, cx);
|
||||||
|
@ -470,14 +485,24 @@ impl AssistantPanel {
|
||||||
&mut self,
|
&mut self,
|
||||||
inline_assist_id: usize,
|
inline_assist_id: usize,
|
||||||
user_prompt: &str,
|
user_prompt: &str,
|
||||||
|
include_conversation: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
self.include_conversation_in_next_inline_assist = include_conversation;
|
||||||
|
|
||||||
let api_key = if let Some(api_key) = self.api_key.borrow().clone() {
|
let api_key = if let Some(api_key) = self.api_key.borrow().clone() {
|
||||||
api_key
|
api_key
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let conversation = if include_conversation {
|
||||||
|
self.active_editor()
|
||||||
|
.map(|editor| editor.read(cx).conversation.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let pending_assist =
|
let pending_assist =
|
||||||
if let Some(pending_assist) = self.pending_inline_assists.get_mut(&inline_assist_id) {
|
if let Some(pending_assist) = self.pending_inline_assists.get_mut(&inline_assist_id) {
|
||||||
pending_assist
|
pending_assist
|
||||||
|
@ -626,14 +651,25 @@ impl AssistantPanel {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let request = OpenAIRequest {
|
let mut request = OpenAIRequest {
|
||||||
model: model.full_name().into(),
|
model: model.full_name().into(),
|
||||||
messages: vec![RequestMessage {
|
messages: Vec::new(),
|
||||||
role: Role::User,
|
|
||||||
content: prompt,
|
|
||||||
}],
|
|
||||||
stream: true,
|
stream: true,
|
||||||
};
|
};
|
||||||
|
if let Some(conversation) = conversation {
|
||||||
|
let conversation = conversation.read(cx);
|
||||||
|
let buffer = conversation.buffer.read(cx);
|
||||||
|
request.messages.extend(
|
||||||
|
conversation
|
||||||
|
.messages(cx)
|
||||||
|
.map(|message| message.to_open_ai_message(buffer)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.messages.push(RequestMessage {
|
||||||
|
role: Role::User,
|
||||||
|
content: prompt,
|
||||||
|
});
|
||||||
let response = stream_completion(api_key, cx.background().clone(), request);
|
let response = stream_completion(api_key, cx.background().clone(), request);
|
||||||
let editor = editor.downgrade();
|
let editor = editor.downgrade();
|
||||||
|
|
||||||
|
@ -2799,7 +2835,10 @@ impl Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InlineAssistantEvent {
|
enum InlineAssistantEvent {
|
||||||
Confirmed { prompt: String },
|
Confirmed {
|
||||||
|
prompt: String,
|
||||||
|
include_conversation: bool,
|
||||||
|
},
|
||||||
Canceled,
|
Canceled,
|
||||||
Dismissed,
|
Dismissed,
|
||||||
}
|
}
|
||||||
|
@ -2815,6 +2854,8 @@ struct InlineAssistant {
|
||||||
prompt_editor: ViewHandle<Editor>,
|
prompt_editor: ViewHandle<Editor>,
|
||||||
confirmed: bool,
|
confirmed: bool,
|
||||||
has_focus: bool,
|
has_focus: bool,
|
||||||
|
include_conversation: bool,
|
||||||
|
measurements: Rc<Cell<BlockMeasurements>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for InlineAssistant {
|
impl Entity for InlineAssistant {
|
||||||
|
@ -2827,9 +2868,55 @@ impl View for InlineAssistant {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
ChildView::new(&self.prompt_editor, cx)
|
let theme = theme::current(cx);
|
||||||
.aligned()
|
|
||||||
.left()
|
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()
|
||||||
|
.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),
|
||||||
|
)
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.assistant.inline.container)
|
||||||
|
.into_any()
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2862,10 +2949,29 @@ impl InlineAssistant {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
cx.emit(InlineAssistantEvent::Confirmed { prompt });
|
cx.emit(InlineAssistantEvent::Confirmed {
|
||||||
|
prompt,
|
||||||
|
include_conversation: self.include_conversation,
|
||||||
|
});
|
||||||
self.confirmed = true;
|
self.confirmed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_include_conversation(
|
||||||
|
&mut self,
|
||||||
|
_: &ToggleIncludeConversation,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.include_conversation = !self.include_conversation;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingInlineAssist {
|
struct PendingInlineAssist {
|
||||||
|
|
|
@ -1160,6 +1160,7 @@ pub struct InlineAssistantStyle {
|
||||||
pub editor: FieldEditor,
|
pub editor: FieldEditor,
|
||||||
pub disabled_editor: FieldEditor,
|
pub disabled_editor: FieldEditor,
|
||||||
pub pending_edit_background: Color,
|
pub pending_edit_background: Color,
|
||||||
|
pub include_conversation: ToggleIconButtonStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default, JsonSchema)]
|
#[derive(Clone, Deserialize, Default, JsonSchema)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { text, border, background, foreground, TextStyle } from "./components"
|
import { text, border, background, foreground, TextStyle } from "./components"
|
||||||
import { Interactive, interactive } from "../element"
|
import { Interactive, interactive, toggleable } from "../element"
|
||||||
import { tab_bar_button } from "../component/tab_bar_button"
|
import { tab_bar_button } from "../component/tab_bar_button"
|
||||||
import { StyleSets, useTheme } from "../theme"
|
import { StyleSets, useTheme } from "../theme"
|
||||||
|
|
||||||
|
@ -80,6 +80,62 @@ export default function assistant(): any {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pending_edit_background: background(theme.highest, "positive"),
|
pending_edit_background: background(theme.highest, "positive"),
|
||||||
|
include_conversation: 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"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
message_header: {
|
message_header: {
|
||||||
margin: { bottom: 4, top: 4 },
|
margin: { bottom: 4, top: 4 },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue