agent: Add several UX improvements (#29828)
Still a work in progress. Release Notes: - N/A --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Nathan Sobo <1789+nathansobo@users.noreply.github.com> Co-authored-by: Cole Miller <53574922+cole-miller@users.noreply.github.com>
This commit is contained in:
parent
5053562e28
commit
10a7f2a972
11 changed files with 219 additions and 109 deletions
|
@ -2,10 +2,9 @@ use crate::context::{AgentContextHandle, RULES_ICON};
|
|||
use crate::context_picker::{ContextPicker, MentionLink};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::message_editor::insert_message_creases;
|
||||
use crate::thread::{
|
||||
LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, QueueState, Thread,
|
||||
ThreadError, ThreadEvent, ThreadFeedback,
|
||||
LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent,
|
||||
ThreadFeedback,
|
||||
};
|
||||
use crate::thread_store::{RulesLoadingError, ThreadStore};
|
||||
use crate::tool_use::{PendingToolUseStatus, ToolUse};
|
||||
|
@ -1243,7 +1242,6 @@ impl ActiveThread {
|
|||
&mut self,
|
||||
message_id: MessageId,
|
||||
message_segments: &[MessageSegment],
|
||||
message_creases: &[MessageCrease],
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
|
@ -1262,7 +1260,6 @@ impl ActiveThread {
|
|||
);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_text(message_text.clone(), window, cx);
|
||||
insert_message_creases(editor, message_creases, &self.context_store, window, cx);
|
||||
editor.focus_handle(cx).focus(window);
|
||||
editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
|
||||
});
|
||||
|
@ -1710,7 +1707,6 @@ impl ActiveThread {
|
|||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
let message_creases = message.creases.clone();
|
||||
|
||||
let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
|
||||
return Empty.into_any();
|
||||
|
@ -1729,33 +1725,13 @@ impl ActiveThread {
|
|||
let tool_uses = thread.tool_uses_for_message(message_id, cx);
|
||||
let has_tool_uses = !tool_uses.is_empty();
|
||||
let is_generating = thread.is_generating();
|
||||
let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
|
||||
|
||||
let is_first_message = ix == 0;
|
||||
let is_last_message = ix == self.messages.len() - 1;
|
||||
|
||||
let show_feedback = thread.is_turn_end(ix);
|
||||
|
||||
let generating_label = is_last_message
|
||||
.then(|| match (thread.queue_state(), is_generating) {
|
||||
(Some(QueueState::Sending), _) => Some(
|
||||
AnimatedLabel::new("Sending")
|
||||
.size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
(Some(QueueState::Queued { position }), _) => Some(
|
||||
Label::new(format!("Queue position: {position}"))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
),
|
||||
(_, true) => Some(
|
||||
AnimatedLabel::new("Generating")
|
||||
.size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
.flatten();
|
||||
let loading_dots = (is_generating_stale && is_last_message)
|
||||
.then(|| AnimatedLabel::new("").size(LabelSize::Small));
|
||||
|
||||
let editing_message_state = self
|
||||
.editing_message
|
||||
|
@ -1778,6 +1754,8 @@ impl ActiveThread {
|
|||
// For all items that should be aligned with the LLM's response.
|
||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||
|
||||
let show_feedback = thread.is_turn_end(ix);
|
||||
|
||||
let feedback_container = h_flex()
|
||||
.group("feedback_container")
|
||||
.mt_1()
|
||||
|
@ -1925,7 +1903,6 @@ impl ActiveThread {
|
|||
open_context(&context, workspace, window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
cx.stop_propagation();
|
||||
}
|
||||
})),
|
||||
)
|
||||
|
@ -2011,13 +1988,15 @@ impl ActiveThread {
|
|||
)
|
||||
}),
|
||||
)
|
||||
.when(editing_message_state.is_none(), |this| {
|
||||
this.tooltip(Tooltip::text("Click To Edit"))
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let message_segments = message.segments.clone();
|
||||
move |this, _, window, cx| {
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
&message_segments,
|
||||
&message_creases,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
@ -2053,80 +2032,84 @@ impl ActiveThread {
|
|||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.when_some(checkpoint, |parent, checkpoint| {
|
||||
let mut is_pending = false;
|
||||
let mut error = None;
|
||||
if let Some(last_restore_checkpoint) =
|
||||
self.thread.read(cx).last_restore_checkpoint()
|
||||
{
|
||||
if last_restore_checkpoint.message_id() == message_id {
|
||||
match last_restore_checkpoint {
|
||||
LastRestoreCheckpoint::Pending { .. } => is_pending = true,
|
||||
LastRestoreCheckpoint::Error { error: err, .. } => {
|
||||
error = Some(err.clone());
|
||||
.map(|parent| {
|
||||
if let Some(checkpoint) = checkpoint.filter(|_| is_generating) {
|
||||
let mut is_pending = false;
|
||||
let mut error = None;
|
||||
if let Some(last_restore_checkpoint) =
|
||||
self.thread.read(cx).last_restore_checkpoint()
|
||||
{
|
||||
if last_restore_checkpoint.message_id() == message_id {
|
||||
match last_restore_checkpoint {
|
||||
LastRestoreCheckpoint::Pending { .. } => is_pending = true,
|
||||
LastRestoreCheckpoint::Error { error: err, .. } => {
|
||||
error = Some(err.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let restore_checkpoint_button =
|
||||
Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
|
||||
.icon(if error.is_some() {
|
||||
IconName::XCircle
|
||||
} else {
|
||||
IconName::Undo
|
||||
})
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(if error.is_some() {
|
||||
Some(Color::Error)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.label_size(LabelSize::XSmall)
|
||||
.disabled(is_pending)
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
this.thread.update(cx, |thread, cx| {
|
||||
thread
|
||||
.restore_checkpoint(checkpoint.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}));
|
||||
let restore_checkpoint_button =
|
||||
Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
|
||||
.icon(if error.is_some() {
|
||||
IconName::XCircle
|
||||
} else {
|
||||
IconName::Undo
|
||||
})
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(if error.is_some() {
|
||||
Some(Color::Error)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.label_size(LabelSize::XSmall)
|
||||
.disabled(is_pending)
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
this.thread.update(cx, |thread, cx| {
|
||||
thread
|
||||
.restore_checkpoint(checkpoint.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}));
|
||||
|
||||
let restore_checkpoint_button = if is_pending {
|
||||
restore_checkpoint_button
|
||||
.with_animation(
|
||||
("pulsating-restore-checkpoint-button", ix),
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.6, 1.)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else if let Some(error) = error {
|
||||
restore_checkpoint_button
|
||||
.tooltip(Tooltip::text(error.to_string()))
|
||||
.into_any_element()
|
||||
let restore_checkpoint_button = if is_pending {
|
||||
restore_checkpoint_button
|
||||
.with_animation(
|
||||
("pulsating-restore-checkpoint-button", ix),
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.6, 1.)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else if let Some(error) = error {
|
||||
restore_checkpoint_button
|
||||
.tooltip(Tooltip::text(error.to_string()))
|
||||
.into_any_element()
|
||||
} else {
|
||||
restore_checkpoint_button.into_any_element()
|
||||
};
|
||||
|
||||
parent.child(
|
||||
h_flex()
|
||||
.pt_2p5()
|
||||
.px_2p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(ui::Divider::horizontal())
|
||||
.child(restore_checkpoint_button)
|
||||
.child(ui::Divider::horizontal()),
|
||||
)
|
||||
} else {
|
||||
restore_checkpoint_button.into_any_element()
|
||||
};
|
||||
|
||||
parent.child(
|
||||
h_flex()
|
||||
.pt_2p5()
|
||||
.px_2p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(ui::Divider::horizontal())
|
||||
.child(restore_checkpoint_button)
|
||||
.child(ui::Divider::horizontal()),
|
||||
)
|
||||
parent
|
||||
}
|
||||
})
|
||||
.when(is_first_message, |parent| {
|
||||
parent.child(self.render_rules_item(cx))
|
||||
})
|
||||
.child(styled_message)
|
||||
.when_some(generating_label, |this, generating_label| {
|
||||
.when(is_generating && is_last_message, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.h_8()
|
||||
|
@ -2134,7 +2117,7 @@ impl ActiveThread {
|
|||
.mb_4()
|
||||
.ml_4()
|
||||
.py_1p5()
|
||||
.child(generating_label),
|
||||
.when_some(loading_dots, |this, loading_dots| this.child(loading_dots)),
|
||||
)
|
||||
})
|
||||
.when(show_feedback, move |parent| {
|
||||
|
@ -2385,7 +2368,6 @@ impl ActiveThread {
|
|||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(text, workspace.clone(), window, cx);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}))
|
||||
.into_any_element()
|
||||
|
|
|
@ -482,7 +482,13 @@ impl ContextPicker {
|
|||
return vec![];
|
||||
};
|
||||
|
||||
recent_context_picker_entries(context_store, self.thread_store.clone(), workspace, cx)
|
||||
recent_context_picker_entries(
|
||||
context_store,
|
||||
self.thread_store.clone(),
|
||||
workspace,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn notify_current_picker(&mut self, cx: &mut Context<Self>) {
|
||||
|
@ -578,11 +584,12 @@ fn recent_context_picker_entries(
|
|||
context_store: Entity<ContextStore>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
workspace: Entity<Workspace>,
|
||||
exclude_path: Option<ProjectPath>,
|
||||
cx: &App,
|
||||
) -> Vec<RecentEntry> {
|
||||
let mut recent = Vec::with_capacity(6);
|
||||
|
||||
let current_files = context_store.read(cx).file_paths(cx);
|
||||
let mut current_files = context_store.read(cx).file_paths(cx);
|
||||
current_files.extend(exclude_path);
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
|
||||
|
|
|
@ -237,6 +237,7 @@ pub struct ContextPickerCompletionProvider {
|
|||
context_store: WeakEntity<ContextStore>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
editor: WeakEntity<Editor>,
|
||||
excluded_buffer: Option<WeakEntity<Buffer>>,
|
||||
}
|
||||
|
||||
impl ContextPickerCompletionProvider {
|
||||
|
@ -245,12 +246,14 @@ impl ContextPickerCompletionProvider {
|
|||
context_store: WeakEntity<ContextStore>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
editor: WeakEntity<Editor>,
|
||||
exclude_buffer: Option<WeakEntity<Buffer>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
workspace,
|
||||
context_store,
|
||||
thread_store,
|
||||
editor,
|
||||
excluded_buffer: exclude_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -736,10 +739,18 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||
let MentionCompletion { mode, argument, .. } = state;
|
||||
let query = argument.unwrap_or_else(|| "".to_string());
|
||||
|
||||
let excluded_path = self
|
||||
.excluded_buffer
|
||||
.as_ref()
|
||||
.and_then(WeakEntity::upgrade)
|
||||
.and_then(|b| b.read(cx).file())
|
||||
.map(|file| ProjectPath::from_file(file.as_ref(), cx));
|
||||
|
||||
let recent_entries = recent_context_picker_entries(
|
||||
context_store.clone(),
|
||||
thread_store.clone(),
|
||||
workspace.clone(),
|
||||
excluded_path.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
|
@ -772,11 +783,17 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||
.into_iter()
|
||||
.filter_map(|mat| match mat {
|
||||
Match::File(FileMatch { mat, is_recent }) => {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
||||
path: mat.path.clone(),
|
||||
};
|
||||
|
||||
if excluded_path.as_ref() == Some(&project_path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self::completion_for_path(
|
||||
ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
||||
path: mat.path.clone(),
|
||||
},
|
||||
project_path,
|
||||
&mat.path_prefix,
|
||||
is_recent,
|
||||
mat.is_dir,
|
||||
|
@ -1138,6 +1155,7 @@ mod tests {
|
|||
"five.txt": "",
|
||||
"six.txt": "",
|
||||
"seven.txt": "",
|
||||
"eight.txt": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
@ -1164,9 +1182,12 @@ mod tests {
|
|||
separator!("b/five.txt"),
|
||||
separator!("b/six.txt"),
|
||||
separator!("b/seven.txt"),
|
||||
separator!("b/eight.txt"),
|
||||
];
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
for path in paths {
|
||||
workspace
|
||||
let buffer = workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
ProjectPath {
|
||||
|
@ -1181,6 +1202,7 @@ mod tests {
|
|||
})
|
||||
.await
|
||||
.unwrap();
|
||||
opened_editors.push(buffer);
|
||||
}
|
||||
|
||||
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
|
@ -1210,12 +1232,23 @@ mod tests {
|
|||
|
||||
let editor_entity = editor.downgrade();
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
let last_opened_buffer = opened_editors.last().and_then(|editor| {
|
||||
editor
|
||||
.downcast::<Editor>()?
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.as_ref()
|
||||
.map(Entity::downgrade)
|
||||
});
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
||||
workspace.downgrade(),
|
||||
context_store.downgrade(),
|
||||
None,
|
||||
editor_entity,
|
||||
last_opened_buffer,
|
||||
))));
|
||||
});
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ use crate::{RemoveAllContext, ToggleContextPicker};
|
|||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use editor::{
|
||||
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, GutterDimensions, MultiBuffer,
|
||||
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle,
|
||||
GutterDimensions, MultiBuffer,
|
||||
actions::{MoveDown, MoveUp},
|
||||
};
|
||||
use feature_flags::{FeatureFlagAppExt as _, ZedProFeatureFlag};
|
||||
|
@ -849,6 +850,7 @@ impl PromptEditor<BufferCodegen> {
|
|||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
||||
) -> PromptEditor<BufferCodegen> {
|
||||
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
||||
let codegen_buffer = codegen.read(cx).buffer(cx).read(cx).as_singleton();
|
||||
let mode = PromptEditorMode::Buffer {
|
||||
id,
|
||||
codegen,
|
||||
|
@ -872,8 +874,15 @@ impl PromptEditor<BufferCodegen> {
|
|||
editor.set_show_cursor_when_unfocused(true, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
|
||||
editor.register_addon(ContextCreasesAddon::new());
|
||||
editor.set_context_menu_options(ContextMenuOptions {
|
||||
min_entries_visible: 12,
|
||||
max_entries_visible: 12,
|
||||
placement: None,
|
||||
});
|
||||
|
||||
editor
|
||||
});
|
||||
|
||||
let prompt_editor_entity = prompt_editor.downgrade();
|
||||
prompt_editor.update(cx, |editor, _| {
|
||||
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
||||
|
@ -881,6 +890,7 @@ impl PromptEditor<BufferCodegen> {
|
|||
context_store.downgrade(),
|
||||
thread_store.clone(),
|
||||
prompt_editor_entity,
|
||||
codegen_buffer.as_ref().map(Entity::downgrade),
|
||||
))));
|
||||
});
|
||||
|
||||
|
@ -1035,6 +1045,11 @@ impl PromptEditor<TerminalCodegen> {
|
|||
);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
|
||||
editor.set_context_menu_options(ContextMenuOptions {
|
||||
min_entries_visible: 12,
|
||||
max_entries_visible: 12,
|
||||
placement: None,
|
||||
});
|
||||
editor
|
||||
});
|
||||
|
||||
|
@ -1045,6 +1060,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||
context_store.downgrade(),
|
||||
thread_store.clone(),
|
||||
prompt_editor_entity,
|
||||
None,
|
||||
))));
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||
use crate::assistant_model_selector::{AssistantModelSelector, ModelType};
|
||||
use crate::context::{AgentContextKey, ContextCreasesAddon, ContextLoadResult, load_context};
|
||||
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
|
||||
use crate::ui::{AgentPreview, AnimatedLabel};
|
||||
use crate::ui::{AgentPreview, AnimatedLabel, MaxModeTooltip};
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::actions::{MoveUp, Paste};
|
||||
|
@ -116,6 +116,7 @@ pub(crate) fn create_editor(
|
|||
context_store,
|
||||
Some(thread_store),
|
||||
editor_entity,
|
||||
None,
|
||||
))));
|
||||
});
|
||||
editor
|
||||
|
@ -451,7 +452,7 @@ impl MessageEditor {
|
|||
});
|
||||
});
|
||||
}))
|
||||
.tooltip(Tooltip::text("Toggle Max Mode"))
|
||||
.tooltip(|_, cx| cx.new(MaxModeTooltip::new).into())
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -358,6 +358,7 @@ pub struct Thread {
|
|||
feedback: Option<ThreadFeedback>,
|
||||
message_feedback: HashMap<MessageId, ThreadFeedback>,
|
||||
last_auto_capture_at: Option<Instant>,
|
||||
last_received_chunk_at: Option<Instant>,
|
||||
request_callback: Option<
|
||||
Box<dyn FnMut(&LanguageModelRequest, &[Result<LanguageModelCompletionEvent, String>])>,
|
||||
>,
|
||||
|
@ -419,6 +420,7 @@ impl Thread {
|
|||
feedback: None,
|
||||
message_feedback: HashMap::default(),
|
||||
last_auto_capture_at: None,
|
||||
last_received_chunk_at: None,
|
||||
request_callback: None,
|
||||
remaining_turns: u32::MAX,
|
||||
configured_model,
|
||||
|
@ -525,6 +527,7 @@ impl Thread {
|
|||
feedback: None,
|
||||
message_feedback: HashMap::default(),
|
||||
last_auto_capture_at: None,
|
||||
last_received_chunk_at: None,
|
||||
request_callback: None,
|
||||
remaining_turns: u32::MAX,
|
||||
configured_model,
|
||||
|
@ -632,6 +635,19 @@ impl Thread {
|
|||
!self.pending_completions.is_empty() || !self.all_tools_finished()
|
||||
}
|
||||
|
||||
/// Indicates whether streaming of language model events is stale.
|
||||
/// When `is_generating()` is false, this method returns `None`.
|
||||
pub fn is_generation_stale(&self) -> Option<bool> {
|
||||
const STALE_THRESHOLD: u128 = 250;
|
||||
|
||||
self.last_received_chunk_at
|
||||
.map(|instant| instant.elapsed().as_millis() > STALE_THRESHOLD)
|
||||
}
|
||||
|
||||
fn received_chunk(&mut self) {
|
||||
self.last_received_chunk_at = Some(Instant::now());
|
||||
}
|
||||
|
||||
pub fn queue_state(&self) -> Option<QueueState> {
|
||||
self.pending_completions
|
||||
.first()
|
||||
|
@ -1328,6 +1344,8 @@ impl Thread {
|
|||
prompt_id: prompt_id.clone(),
|
||||
};
|
||||
|
||||
self.last_received_chunk_at = Some(Instant::now());
|
||||
|
||||
let task = cx.spawn(async move |thread, cx| {
|
||||
let stream_completion_future = model.stream_completion_with_usage(request, &cx);
|
||||
let initial_token_usage =
|
||||
|
@ -1398,6 +1416,8 @@ impl Thread {
|
|||
current_token_usage = token_usage;
|
||||
}
|
||||
LanguageModelCompletionEvent::Text(chunk) => {
|
||||
thread.received_chunk();
|
||||
|
||||
cx.emit(ThreadEvent::ReceivedTextChunk);
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant
|
||||
|
@ -1426,6 +1446,8 @@ impl Thread {
|
|||
text: chunk,
|
||||
signature,
|
||||
} => {
|
||||
thread.received_chunk();
|
||||
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant
|
||||
&& !thread.tool_use.has_tool_results(last_message.id)
|
||||
|
@ -1512,6 +1534,7 @@ impl Thread {
|
|||
}
|
||||
|
||||
thread.update(cx, |thread, cx| {
|
||||
thread.last_received_chunk_at = None;
|
||||
thread
|
||||
.pending_completions
|
||||
.retain(|completion| completion.id != pending_completion_id);
|
||||
|
|
|
@ -2,6 +2,7 @@ mod agent_notification;
|
|||
pub mod agent_preview;
|
||||
mod animated_label;
|
||||
mod context_pill;
|
||||
mod max_mode_tooltip;
|
||||
mod upsell;
|
||||
mod usage_banner;
|
||||
|
||||
|
@ -9,4 +10,5 @@ pub use agent_notification::*;
|
|||
pub use agent_preview::*;
|
||||
pub use animated_label::*;
|
||||
pub use context_pill::*;
|
||||
pub use max_mode_tooltip::*;
|
||||
pub use usage_banner::*;
|
||||
|
|
33
crates/agent/src/ui/max_mode_tooltip.rs
Normal file
33
crates/agent/src/ui/max_mode_tooltip.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use gpui::{Context, IntoElement, Render, Window};
|
||||
use ui::{prelude::*, tooltip_container};
|
||||
|
||||
pub struct MaxModeTooltip;
|
||||
|
||||
impl MaxModeTooltip {
|
||||
pub fn new(_cx: &mut Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MaxModeTooltip {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
tooltip_container(_window, cx, |this, _, _| {
|
||||
this.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small))
|
||||
.child(Label::new("Zed's Max Mode"))
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.max_w_72()
|
||||
.child(
|
||||
Label::new("This mode enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ use assistant_settings::AssistantSettings;
|
|||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{
|
||||
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
actions::{MoveDown, MoveUp, SelectAll},
|
||||
};
|
||||
use fs::Fs;
|
||||
|
@ -730,6 +730,11 @@ impl PromptEditor {
|
|||
);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(window, cx), cx);
|
||||
editor.set_context_menu_options(ContextMenuOptions {
|
||||
min_entries_visible: 12,
|
||||
max_entries_visible: 12,
|
||||
placement: None,
|
||||
});
|
||||
editor
|
||||
});
|
||||
|
||||
|
|
|
@ -304,6 +304,7 @@ impl EditFileToolCard {
|
|||
editor.set_soft_wrap_mode(SoftWrap::None, cx);
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_show_scrollbars(false, cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_breakpoints(false, cx);
|
||||
editor.set_show_code_actions(false, cx);
|
||||
|
@ -640,7 +641,7 @@ impl ToolCard for EditFileToolCard {
|
|||
.border_t_1()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(div().pl_1().child(editor))
|
||||
.child(editor)
|
||||
.when(
|
||||
!self.full_height_expanded && is_collapsible,
|
||||
|editor_container| editor_container.child(gradient_overlay),
|
||||
|
|
|
@ -318,6 +318,13 @@ pub struct ProjectPath {
|
|||
}
|
||||
|
||||
impl ProjectPath {
|
||||
pub fn from_file(value: &dyn language::File, cx: &App) -> Self {
|
||||
ProjectPath {
|
||||
worktree_id: value.worktree_id(cx),
|
||||
path: value.path().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proto(p: proto::ProjectPath) -> Self {
|
||||
Self {
|
||||
worktree_id: WorktreeId::from_proto(p.worktree_id),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue