mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
agent_ui: Allow selection of commands from tool calls (#50545)
Closes #50427. Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) The changes respect your current theme + mimic the existing UI when it comes to font size+weight. <img width="437" height="131" alt="image" src="https://github.com/user-attachments/assets/89f6c5d0-d1a3-478a-88e9-7d203240416d" /> https://github.com/user-attachments/assets/a5fb6c82-fffd-494f-a374-9296d1690736 Release Notes: - Allow selection of commands from tool calls
This commit is contained in:
parent
1b37531a2e
commit
280f8b1048
3 changed files with 68 additions and 46 deletions
|
|
@ -38,7 +38,9 @@ use gpui::{
|
|||
};
|
||||
use language::Buffer;
|
||||
use language_model::{LanguageModelCompletionError, LanguageModelRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownFont, MarkdownStyle};
|
||||
use markdown::{
|
||||
CodeBlockRenderer, CopyButtonVisibility, Markdown, MarkdownElement, MarkdownFont, MarkdownStyle,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use project::{AgentId, AgentServerStore, Project, ProjectEntryId};
|
||||
use prompt_store::{PromptId, PromptStore};
|
||||
|
|
|
|||
|
|
@ -5910,42 +5910,58 @@ impl ThreadView {
|
|||
&self,
|
||||
group: SharedString,
|
||||
is_preview: bool,
|
||||
command_source: &str,
|
||||
command: Entity<Markdown>,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> Div {
|
||||
v_flex()
|
||||
.group(group.clone())
|
||||
.p_1p5()
|
||||
.bg(self.tool_card_header_bg(cx))
|
||||
.when(is_preview, |this| {
|
||||
this.pt_1().child(
|
||||
// Wrapping this label on a container with 24px height to avoid
|
||||
// layout shift when it changes from being a preview label
|
||||
// to the actual path where the command will run in
|
||||
h_flex().h_6().child(
|
||||
Label::new("Run Command")
|
||||
.buffer_font(cx)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
})
|
||||
.children(command_source.lines().map(|line| {
|
||||
let text: SharedString = if line.is_empty() {
|
||||
" ".into()
|
||||
} else {
|
||||
line.to_string().into()
|
||||
};
|
||||
// The label's markdown source is a fenced code block (```\n...\n```);
|
||||
// strip the fences so the copy button yields just the command text.
|
||||
let command_source = command.read(cx).source();
|
||||
let command_text = command_source
|
||||
.strip_prefix("```\n")
|
||||
.and_then(|s| s.strip_suffix("\n```"))
|
||||
.unwrap_or(&command_source)
|
||||
.to_string();
|
||||
|
||||
Label::new(text).buffer_font(cx).size(LabelSize::Small)
|
||||
}))
|
||||
.child(
|
||||
div().absolute().top_1().right_1().child(
|
||||
CopyButton::new("copy-command", command_source.to_string())
|
||||
.tooltip_label("Copy Command")
|
||||
.visible_on_hover(group),
|
||||
let mut style = MarkdownStyle::themed(MarkdownFont::Agent, window, cx).with_buffer_font(cx);
|
||||
style.container_style.text.font_size = Some(rems_from_px(12.).into());
|
||||
style.container_style.text.line_height = Some(rems_from_px(17.).into());
|
||||
style.height_is_multiple_of_line_height = true;
|
||||
|
||||
let header_bg = self.tool_card_header_bg(cx);
|
||||
let run_command_label = if is_preview {
|
||||
Some(
|
||||
h_flex().h_6().child(
|
||||
Label::new("Run Command")
|
||||
.buffer_font(cx)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Suppress the code block's built-in copy button so we don't stack two
|
||||
// copy buttons on top of each other; the outer button below is the one
|
||||
// we want, because it copies the unfenced command text.
|
||||
let markdown_element =
|
||||
self.render_markdown(command, style)
|
||||
.code_block_renderer(CodeBlockRenderer::Default {
|
||||
copy_button_visibility: CopyButtonVisibility::Hidden,
|
||||
border: false,
|
||||
});
|
||||
let copy_button = CopyButton::new("copy-command", command_text)
|
||||
.tooltip_label("Copy Command")
|
||||
.visible_on_hover(group.clone());
|
||||
|
||||
v_flex()
|
||||
.group(group)
|
||||
.relative()
|
||||
.p_1p5()
|
||||
.bg(header_bg)
|
||||
.when(is_preview, |this| this.pt_1().children(run_command_label))
|
||||
.child(markdown_element)
|
||||
.child(div().absolute().top_1().right_1().child(copy_button))
|
||||
}
|
||||
|
||||
fn render_terminal_tool_call(
|
||||
|
|
@ -5961,7 +5977,6 @@ impl ThreadView {
|
|||
) -> AnyElement {
|
||||
let terminal_data = terminal.read(cx);
|
||||
let working_dir = terminal_data.working_dir();
|
||||
let command = terminal_data.command();
|
||||
let started_at = terminal_data.started_at();
|
||||
|
||||
let tool_failed = matches!(
|
||||
|
|
@ -6012,17 +6027,13 @@ impl ThreadView {
|
|||
.map(|path| path.display().to_string())
|
||||
.unwrap_or_else(|| "current directory".to_string());
|
||||
|
||||
// Since the command's source is wrapped in a markdown code block
|
||||
// (```\n...\n```), we need to strip that so we're left with only the
|
||||
// command's content.
|
||||
let command_source = command.read(cx).source();
|
||||
let command_content = command_source
|
||||
.strip_prefix("```\n")
|
||||
.and_then(|s| s.strip_suffix("\n```"))
|
||||
.unwrap_or(&command_source);
|
||||
|
||||
let command_element =
|
||||
self.render_collapsible_command(header_group.clone(), false, command_content, cx);
|
||||
let command_element = self.render_collapsible_command(
|
||||
header_group.clone(),
|
||||
false,
|
||||
tool_call.label.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let is_expanded = self.expanded_tool_calls.contains(&tool_call.id);
|
||||
|
||||
|
|
@ -6565,11 +6576,11 @@ impl ThreadView {
|
|||
})
|
||||
.map(|this| {
|
||||
if is_terminal_tool {
|
||||
let label_source = tool_call.label.read(cx).source();
|
||||
this.child(self.render_collapsible_command(
|
||||
card_header_id.clone(),
|
||||
true,
|
||||
label_source,
|
||||
tool_call.label.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -297,6 +297,15 @@ impl MarkdownStyle {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_buffer_font(mut self, cx: &App) -> Self {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
self.base_text_style.font_family = theme_settings.buffer_font.family.clone();
|
||||
self.base_text_style.font_fallbacks = theme_settings.buffer_font.fallbacks.clone();
|
||||
self.base_text_style.font_features = theme_settings.buffer_font.features.clone();
|
||||
self.base_text_style.font_weight = theme_settings.buffer_font.weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_muted_text(mut self, cx: &App) -> Self {
|
||||
let colors = cx.theme().colors();
|
||||
self.base_text_style.color = colors.text_muted;
|
||||
|
|
|
|||
Loading…
Reference in a new issue