mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 19:10:24 +00:00
Show inline assistant errors
This commit is contained in:
parent
87e25c8c23
commit
5c498c8610
1 changed files with 125 additions and 35 deletions
|
@ -40,7 +40,7 @@ use std::{
|
|||
cell::{Cell, RefCell},
|
||||
cmp, env,
|
||||
fmt::Write,
|
||||
iter,
|
||||
future, iter,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
|
@ -55,7 +55,7 @@ use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
|
|||
use workspace::{
|
||||
dock::{DockPosition, Panel},
|
||||
searchable::Direction,
|
||||
Save, ToggleZoom, Toolbar, Workspace,
|
||||
Save, Toast, ToggleZoom, Toolbar, Workspace,
|
||||
};
|
||||
|
||||
actions!(
|
||||
|
@ -290,6 +290,7 @@ impl AssistantPanel {
|
|||
has_focus: false,
|
||||
include_conversation: self.include_conversation_in_next_inline_assist,
|
||||
measurements: measurements.clone(),
|
||||
error: None,
|
||||
};
|
||||
cx.focus_self();
|
||||
assistant
|
||||
|
@ -331,7 +332,7 @@ impl AssistantPanel {
|
|||
editor: editor.downgrade(),
|
||||
range,
|
||||
highlighted_ranges: Default::default(),
|
||||
inline_assistant_block_id: Some(block_id),
|
||||
inline_assistant: Some((block_id, inline_assistant.clone())),
|
||||
code_generation: Task::ready(None),
|
||||
transaction_id: None,
|
||||
_subscriptions: vec![
|
||||
|
@ -477,7 +478,7 @@ impl AssistantPanel {
|
|||
fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
|
||||
if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
|
||||
if let Some(editor) = pending_assist.editor.upgrade(cx) {
|
||||
if let Some(block_id) = pending_assist.inline_assistant_block_id.take() {
|
||||
if let Some((block_id, _)) = pending_assist.inline_assistant.take() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
});
|
||||
|
@ -699,22 +700,17 @@ impl AssistantPanel {
|
|||
|
||||
pending_assist.code_generation = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let _cleanup = util::defer({
|
||||
let mut cx = cx.clone();
|
||||
let this = this.clone();
|
||||
move || {
|
||||
let _ = this.update(&mut cx, |this, cx| {
|
||||
this.close_inline_assist(inline_assist_id, false, cx)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let mut edit_start = range.start.to_offset(&snapshot);
|
||||
|
||||
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
|
||||
let diff = cx.background().spawn(async move {
|
||||
let chunks = strip_markdown_codeblock(response.await?.filter_map(
|
||||
|message| async move { message.ok()?.choices.pop()?.delta.content },
|
||||
|message| async move {
|
||||
match message {
|
||||
Ok(mut message) => Some(Ok(message.choices.pop()?.delta.content?)),
|
||||
Err(error) => Some(Err(error)),
|
||||
}
|
||||
},
|
||||
));
|
||||
futures::pin_mut!(chunks);
|
||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||
|
@ -737,6 +733,7 @@ impl AssistantPanel {
|
|||
let mut new_text = String::new();
|
||||
|
||||
while let Some(chunk) = chunks.next().await {
|
||||
let chunk = chunk?;
|
||||
if first_chunk && (chunk.starts_with(' ') || chunk.starts_with('\t')) {
|
||||
autoindent = false;
|
||||
}
|
||||
|
@ -771,9 +768,17 @@ impl AssistantPanel {
|
|||
});
|
||||
|
||||
while let Some(hunks) = hunks_rx.next().await {
|
||||
let editor = editor
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("editor was dropped"))?;
|
||||
let editor = if let Some(editor) = editor.upgrade(&cx) {
|
||||
editor
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
let this = if let Some(this) = this.upgrade(&cx) {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let pending_assist = if let Some(pending_assist) =
|
||||
|
@ -840,9 +845,42 @@ impl AssistantPanel {
|
|||
});
|
||||
|
||||
this.update_highlights_for_editor(&editor, cx);
|
||||
})?;
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(error) = diff.await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let pending_assist = if let Some(pending_assist) =
|
||||
this.pending_inline_assists.get_mut(&inline_assist_id)
|
||||
{
|
||||
pending_assist
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some((_, inline_assistant)) =
|
||||
pending_assist.inline_assistant.as_ref()
|
||||
{
|
||||
inline_assistant.update(cx, |inline_assistant, cx| {
|
||||
inline_assistant.set_error(error, cx);
|
||||
});
|
||||
} else if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_toast(
|
||||
Toast::new(
|
||||
inline_assist_id,
|
||||
format!("Inline assistant error: {}", error),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
})?;
|
||||
} else {
|
||||
let _ = this.update(&mut cx, |this, cx| {
|
||||
this.close_inline_assist(inline_assist_id, false, cx)
|
||||
});
|
||||
}
|
||||
diff.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
@ -2856,6 +2894,7 @@ struct InlineAssistant {
|
|||
has_focus: bool,
|
||||
include_conversation: bool,
|
||||
measurements: Rc<Cell<BlockMeasurements>>,
|
||||
error: Option<anyhow::Error>,
|
||||
}
|
||||
|
||||
impl Entity for InlineAssistant {
|
||||
|
@ -2868,17 +2907,42 @@ impl View for InlineAssistant {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum ErrorIcon {}
|
||||
let theme = theme::current(cx);
|
||||
|
||||
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()
|
||||
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 let Some(error) = self.error.as_ref() {
|
||||
Some(
|
||||
Svg::new("icons/circle_x_mark_12.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,
|
||||
)
|
||||
.aligned(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.aligned()
|
||||
.constrained()
|
||||
.dynamically({
|
||||
|
@ -2954,6 +3018,8 @@ impl InlineAssistant {
|
|||
include_conversation: self.include_conversation,
|
||||
});
|
||||
self.confirmed = true;
|
||||
self.error = None;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2968,6 +3034,19 @@ impl InlineAssistant {
|
|||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_error(&mut self, error: anyhow::Error, cx: &mut ViewContext<Self>) {
|
||||
self.error = Some(error);
|
||||
self.confirmed = false;
|
||||
self.prompt_editor.update(cx, |editor, cx| {
|
||||
editor.set_read_only(false);
|
||||
editor.set_field_editor_style(
|
||||
Some(Arc::new(|theme| theme.assistant.inline.editor.clone())),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// This wouldn't need to exist if we could pass parameters when rendering child views.
|
||||
|
@ -2982,7 +3061,7 @@ struct PendingInlineAssist {
|
|||
editor: WeakViewHandle<Editor>,
|
||||
range: Range<Anchor>,
|
||||
highlighted_ranges: Vec<Range<Anchor>>,
|
||||
inline_assistant_block_id: Option<BlockId>,
|
||||
inline_assistant: Option<(BlockId, ViewHandle<InlineAssistant>)>,
|
||||
code_generation: Task<Option<()>>,
|
||||
transaction_id: Option<TransactionId>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
|
@ -3010,23 +3089,29 @@ fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
|
|||
}
|
||||
}
|
||||
|
||||
fn strip_markdown_codeblock(stream: impl Stream<Item = String>) -> impl Stream<Item = String> {
|
||||
fn strip_markdown_codeblock(
|
||||
stream: impl Stream<Item = Result<String>>,
|
||||
) -> impl Stream<Item = Result<String>> {
|
||||
let mut first_line = true;
|
||||
let mut buffer = String::new();
|
||||
let mut starts_with_fenced_code_block = false;
|
||||
stream.filter_map(move |chunk| {
|
||||
let chunk = match chunk {
|
||||
Ok(chunk) => chunk,
|
||||
Err(err) => return future::ready(Some(Err(err))),
|
||||
};
|
||||
buffer.push_str(&chunk);
|
||||
|
||||
if first_line {
|
||||
if buffer == "" || buffer == "`" || buffer == "``" {
|
||||
return futures::future::ready(None);
|
||||
return future::ready(None);
|
||||
} else if buffer.starts_with("```") {
|
||||
starts_with_fenced_code_block = true;
|
||||
if let Some(newline_ix) = buffer.find('\n') {
|
||||
buffer.replace_range(..newline_ix + 1, "");
|
||||
first_line = false;
|
||||
} else {
|
||||
return futures::future::ready(None);
|
||||
return future::ready(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3050,10 +3135,10 @@ fn strip_markdown_codeblock(stream: impl Stream<Item = String>) -> impl Stream<I
|
|||
let result = if buffer.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(buffer.clone())
|
||||
Some(Ok(buffer.clone()))
|
||||
};
|
||||
buffer = remainder;
|
||||
futures::future::ready(result)
|
||||
future::ready(result)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3434,41 +3519,46 @@ mod tests {
|
|||
async fn test_strip_markdown_codeblock() {
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("Lorem ipsum dolor", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```\nLorem ipsum dolor", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"Lorem ipsum dolor"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("```html\n```js\nLorem ipsum dolor\n```\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"```js\nLorem ipsum dolor\n```"
|
||||
);
|
||||
assert_eq!(
|
||||
strip_markdown_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
|
||||
.map(|chunk| chunk.unwrap())
|
||||
.collect::<String>()
|
||||
.await,
|
||||
"``\nLorem ipsum dolor\n```"
|
||||
);
|
||||
|
||||
fn chunks(text: &str, size: usize) -> impl Stream<Item = String> {
|
||||
fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
|
||||
stream::iter(
|
||||
text.chars()
|
||||
.collect::<Vec<_>>()
|
||||
.chunks(size)
|
||||
.map(|chunk| chunk.iter().collect::<String>())
|
||||
.map(|chunk| Ok(chunk.iter().collect::<String>()))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue