This PR adds the ability to fetch URLs as context in Assistant2. In the picker we use the search area as an input for the user to enter the URL they wish to fetch: <img width="1159" alt="Screenshot 2024-12-13 at 2 45 41 PM" src="https://github.com/user-attachments/assets/b3b20648-2c22-4509-b592-d0291d25b202" /> <img width="1159" alt="Screenshot 2024-12-13 at 2 45 47 PM" src="https://github.com/user-attachments/assets/7e6bab2d-2731-467f-9781-130c6e4ea5cf" /> Release Notes: - N/A
228 lines
7.5 KiB
Rust
228 lines
7.5 KiB
Rust
mod fetch_context_picker;
|
|
mod file_context_picker;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use gpui::{
|
|
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, SharedString, Task, View,
|
|
WeakView,
|
|
};
|
|
use picker::{Picker, PickerDelegate};
|
|
use ui::{prelude::*, ListItem, ListItemSpacing, Tooltip};
|
|
use util::ResultExt;
|
|
use workspace::Workspace;
|
|
|
|
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
|
use crate::context_picker::file_context_picker::FileContextPicker;
|
|
use crate::message_editor::MessageEditor;
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum ContextPickerMode {
|
|
Default,
|
|
File(View<FileContextPicker>),
|
|
Fetch(View<FetchContextPicker>),
|
|
}
|
|
|
|
pub(super) struct ContextPicker {
|
|
mode: ContextPickerMode,
|
|
picker: View<Picker<ContextPickerDelegate>>,
|
|
}
|
|
|
|
impl ContextPicker {
|
|
pub fn new(
|
|
workspace: WeakView<Workspace>,
|
|
message_editor: WeakView<MessageEditor>,
|
|
cx: &mut ViewContext<Self>,
|
|
) -> Self {
|
|
let delegate = ContextPickerDelegate {
|
|
context_picker: cx.view().downgrade(),
|
|
workspace: workspace.clone(),
|
|
message_editor: message_editor.clone(),
|
|
entries: vec![
|
|
ContextPickerEntry {
|
|
name: "directory".into(),
|
|
description: "Insert any directory".into(),
|
|
icon: IconName::Folder,
|
|
},
|
|
ContextPickerEntry {
|
|
name: "file".into(),
|
|
description: "Insert any file".into(),
|
|
icon: IconName::File,
|
|
},
|
|
ContextPickerEntry {
|
|
name: "fetch".into(),
|
|
description: "Fetch content from URL".into(),
|
|
icon: IconName::Globe,
|
|
},
|
|
],
|
|
selected_ix: 0,
|
|
};
|
|
|
|
let picker = cx.new_view(|cx| {
|
|
Picker::nonsearchable_uniform_list(delegate, cx).max_height(Some(rems(20.).into()))
|
|
});
|
|
|
|
ContextPicker {
|
|
mode: ContextPickerMode::Default,
|
|
picker,
|
|
}
|
|
}
|
|
|
|
pub fn reset_mode(&mut self) {
|
|
self.mode = ContextPickerMode::Default;
|
|
}
|
|
}
|
|
|
|
impl EventEmitter<DismissEvent> for ContextPicker {}
|
|
|
|
impl FocusableView for ContextPicker {
|
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
|
match &self.mode {
|
|
ContextPickerMode::Default => self.picker.focus_handle(cx),
|
|
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
|
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Render for ContextPicker {
|
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
v_flex()
|
|
.w(px(400.))
|
|
.min_w(px(400.))
|
|
.map(|parent| match &self.mode {
|
|
ContextPickerMode::Default => parent.child(self.picker.clone()),
|
|
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
|
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct ContextPickerEntry {
|
|
name: SharedString,
|
|
description: SharedString,
|
|
icon: IconName,
|
|
}
|
|
|
|
pub(crate) struct ContextPickerDelegate {
|
|
context_picker: WeakView<ContextPicker>,
|
|
workspace: WeakView<Workspace>,
|
|
message_editor: WeakView<MessageEditor>,
|
|
entries: Vec<ContextPickerEntry>,
|
|
selected_ix: usize,
|
|
}
|
|
|
|
impl PickerDelegate for ContextPickerDelegate {
|
|
type ListItem = ListItem;
|
|
|
|
fn match_count(&self) -> usize {
|
|
self.entries.len()
|
|
}
|
|
|
|
fn selected_index(&self) -> usize {
|
|
self.selected_ix
|
|
}
|
|
|
|
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
|
self.selected_ix = ix.min(self.entries.len().saturating_sub(1));
|
|
cx.notify();
|
|
}
|
|
|
|
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
|
"Select a context source…".into()
|
|
}
|
|
|
|
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
|
Task::ready(())
|
|
}
|
|
|
|
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
|
if let Some(entry) = self.entries.get(self.selected_ix) {
|
|
self.context_picker
|
|
.update(cx, |this, cx| {
|
|
match entry.name.to_string().as_str() {
|
|
"file" => {
|
|
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
|
FileContextPicker::new(
|
|
self.context_picker.clone(),
|
|
self.workspace.clone(),
|
|
self.message_editor.clone(),
|
|
cx,
|
|
)
|
|
}));
|
|
}
|
|
"fetch" => {
|
|
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
|
FetchContextPicker::new(
|
|
self.context_picker.clone(),
|
|
self.workspace.clone(),
|
|
self.message_editor.clone(),
|
|
cx,
|
|
)
|
|
}));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
cx.focus_self();
|
|
})
|
|
.log_err();
|
|
}
|
|
}
|
|
|
|
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
|
self.context_picker
|
|
.update(cx, |this, cx| match this.mode {
|
|
ContextPickerMode::Default => cx.emit(DismissEvent),
|
|
ContextPickerMode::File(_) | ContextPickerMode::Fetch(_) => {}
|
|
})
|
|
.log_err();
|
|
}
|
|
|
|
fn render_match(
|
|
&self,
|
|
ix: usize,
|
|
selected: bool,
|
|
_cx: &mut ViewContext<Picker<Self>>,
|
|
) -> Option<Self::ListItem> {
|
|
let entry = &self.entries[ix];
|
|
|
|
Some(
|
|
ListItem::new(ix)
|
|
.inset(true)
|
|
.spacing(ListItemSpacing::Dense)
|
|
.toggle_state(selected)
|
|
.tooltip({
|
|
let description = entry.description.clone();
|
|
move |cx| cx.new_view(|_cx| Tooltip::new(description.clone())).into()
|
|
})
|
|
.child(
|
|
v_flex()
|
|
.group(format!("context-entry-label-{ix}"))
|
|
.w_full()
|
|
.py_0p5()
|
|
.min_w(px(250.))
|
|
.max_w(px(400.))
|
|
.child(
|
|
h_flex()
|
|
.gap_1p5()
|
|
.child(Icon::new(entry.icon).size(IconSize::XSmall))
|
|
.child(
|
|
Label::new(entry.name.clone())
|
|
.single_line()
|
|
.size(LabelSize::Small),
|
|
),
|
|
)
|
|
.child(
|
|
div().overflow_hidden().text_ellipsis().child(
|
|
Label::new(entry.description.clone())
|
|
.size(LabelSize::Small)
|
|
.color(Color::Muted),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
}
|