thread view: Add improvements to the UI (#36680)

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-08-21 17:05:29 -03:00 committed by GitHub
parent 2234f91b7b
commit 555692fac6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 269 additions and 49 deletions

View file

@ -41,7 +41,7 @@ use text::Anchor;
use theme::ThemeSettings;
use ui::{
Callout, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle,
Scrollbar, ScrollbarState, Tooltip, prelude::*,
Scrollbar, ScrollbarState, SpinnerLabel, Tooltip, prelude::*,
};
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
use workspace::{CollaboratorId, Workspace};
@ -1205,7 +1205,7 @@ impl AcpThreadView {
div()
.py_3()
.px_2()
.rounded_lg()
.rounded_md()
.shadow_md()
.bg(cx.theme().colors().editor_background)
.border_1()
@ -1263,7 +1263,7 @@ impl AcpThreadView {
.into_any()
}
AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) => {
let style = default_markdown_style(false, window, cx);
let style = default_markdown_style(false, false, window, cx);
let message_body = v_flex()
.w_full()
.gap_2p5()
@ -1398,8 +1398,6 @@ impl AcpThreadView {
.relative()
.w_full()
.gap_1p5()
.opacity(0.8)
.hover(|style| style.opacity(1.))
.child(
h_flex()
.size_4()
@ -1440,6 +1438,7 @@ impl AcpThreadView {
.child(
div()
.text_size(self.tool_name_font_size())
.text_color(cx.theme().colors().text_muted)
.child("Thinking"),
)
.on_click(cx.listener({
@ -1463,9 +1462,10 @@ impl AcpThreadView {
.border_l_1()
.border_color(self.tool_card_border_color(cx))
.text_ui_sm(cx)
.child(
self.render_markdown(chunk, default_markdown_style(false, window, cx)),
),
.child(self.render_markdown(
chunk,
default_markdown_style(false, false, window, cx),
)),
)
})
.into_any_element()
@ -1555,11 +1555,11 @@ impl AcpThreadView {
| ToolCallStatus::Completed => None,
ToolCallStatus::InProgress => Some(
Icon::new(IconName::ArrowCircle)
.color(Color::Accent)
.color(Color::Muted)
.size(IconSize::Small)
.with_animation(
"running",
Animation::new(Duration::from_secs(2)).repeat(),
Animation::new(Duration::from_secs(3)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any(),
@ -1572,6 +1572,10 @@ impl AcpThreadView {
),
};
let failed_tool_call = matches!(
tool_call.status,
ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed
);
let needs_confirmation = matches!(
tool_call.status,
ToolCallStatus::WaitingForConfirmation { .. }
@ -1652,7 +1656,7 @@ impl AcpThreadView {
v_flex()
.when(use_card_layout, |this| {
this.rounded_lg()
this.rounded_md()
.border_1()
.border_color(self.tool_card_border_color(cx))
.bg(cx.theme().colors().editor_background)
@ -1664,20 +1668,16 @@ impl AcpThreadView {
.w_full()
.gap_1()
.justify_between()
.map(|this| {
if use_card_layout {
this.pl_2()
.pr_1p5()
.py_1()
.rounded_t_md()
.when(is_open, |this| {
this.border_b_1()
.border_color(self.tool_card_border_color(cx))
})
.bg(self.tool_card_header_bg(cx))
} else {
this.opacity(0.8).hover(|style| style.opacity(1.))
}
.when(use_card_layout, |this| {
this.pl_2()
.pr_1p5()
.py_1()
.rounded_t_md()
.when(is_open && !failed_tool_call, |this| {
this.border_b_1()
.border_color(self.tool_card_border_color(cx))
})
.bg(self.tool_card_header_bg(cx))
})
.child(
h_flex()
@ -1709,13 +1709,15 @@ impl AcpThreadView {
.px_1p5()
.rounded_sm()
.overflow_x_scroll()
.opacity(0.8)
.hover(|label| {
label.opacity(1.).bg(cx
.theme()
.colors()
.element_hover
.opacity(0.5))
label.bg(cx.theme().colors().element_hover.opacity(0.5))
})
.map(|this| {
if use_card_layout {
this.text_color(cx.theme().colors().text)
} else {
this.text_color(cx.theme().colors().text_muted)
}
})
.child(name)
.tooltip(Tooltip::text("Jump to File"))
@ -1738,7 +1740,7 @@ impl AcpThreadView {
.overflow_x_scroll()
.child(self.render_markdown(
tool_call.label.clone(),
default_markdown_style(false, window, cx),
default_markdown_style(false, true, window, cx),
)),
)
.child(gradient_overlay(gradient_color))
@ -1804,9 +1806,9 @@ impl AcpThreadView {
.border_color(self.tool_card_border_color(cx))
.text_sm()
.text_color(cx.theme().colors().text_muted)
.child(self.render_markdown(markdown, default_markdown_style(false, window, cx)))
.child(self.render_markdown(markdown, default_markdown_style(false, false, window, cx)))
.child(
Button::new(button_id, "Collapse Output")
Button::new(button_id, "Collapse")
.full_width()
.style(ButtonStyle::Outlined)
.label_size(LabelSize::Small)
@ -2131,7 +2133,7 @@ impl AcpThreadView {
.to_string()
} else {
format!(
"Output is {} longto avoid unexpected token usage, \
"Output is {} long, and to avoid unexpected token usage, \
only 16 KB was sent back to the model.",
format_file_size(output.original_content_len as u64, true),
)
@ -2199,7 +2201,7 @@ impl AcpThreadView {
.border_1()
.when(tool_failed || command_failed, |card| card.border_dashed())
.border_color(border_color)
.rounded_lg()
.rounded_md()
.overflow_hidden()
.child(
v_flex()
@ -2553,9 +2555,10 @@ impl AcpThreadView {
.into_any(),
)
.children(description.map(|desc| {
div().text_ui(cx).text_center().child(
self.render_markdown(desc.clone(), default_markdown_style(false, window, cx)),
)
div().text_ui(cx).text_center().child(self.render_markdown(
desc.clone(),
default_markdown_style(false, false, window, cx),
))
}))
.children(
configuration_view
@ -3379,7 +3382,7 @@ impl AcpThreadView {
"used-tokens-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.6, 1.)),
.with_easing(pulsating_between(0.3, 0.8)),
|label, delta| label.alpha(delta),
)
.into_any()
@ -4636,9 +4639,9 @@ impl Render for AcpThreadView {
ThreadStatus::Idle
| ThreadStatus::WaitingForToolConfirmation => None,
ThreadStatus::Generating => div()
.px_5()
.py_2()
.child(LoadingLabel::new("").size(LabelSize::Small))
.px(rems_from_px(22.))
.child(SpinnerLabel::new().size(LabelSize::Small))
.into(),
},
)
@ -4671,7 +4674,12 @@ impl Render for AcpThreadView {
}
}
fn default_markdown_style(buffer_font: bool, window: &Window, cx: &App) -> MarkdownStyle {
fn default_markdown_style(
buffer_font: bool,
muted_text: bool,
window: &Window,
cx: &App,
) -> MarkdownStyle {
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
@ -4692,20 +4700,26 @@ fn default_markdown_style(buffer_font: bool, window: &Window, cx: &App) -> Markd
TextSize::Default.rems(cx)
};
let text_color = if muted_text {
colors.text_muted
} else {
colors.text
};
text_style.refine(&TextStyleRefinement {
font_family: Some(font_family),
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
font_features: Some(theme_settings.ui_font.features.clone()),
font_size: Some(font_size.into()),
line_height: Some(line_height.into()),
color: Some(cx.theme().colors().text),
color: Some(text_color),
..Default::default()
});
MarkdownStyle {
base_text_style: text_style.clone(),
syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().colors().element_selection_background,
selection_background_color: colors.element_selection_background,
code_block_overflow_x_scroll: true,
table_overflow_x_scroll: true,
heading_level_styles: Some(HeadingLevelStyles {
@ -4791,7 +4805,7 @@ fn plan_label_markdown_style(
window: &Window,
cx: &App,
) -> MarkdownStyle {
let default_md_style = default_markdown_style(false, window, cx);
let default_md_style = default_markdown_style(false, false, window, cx);
MarkdownStyle {
base_text_style: TextStyle {
@ -4811,7 +4825,7 @@ fn plan_label_markdown_style(
}
fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
let default_md_style = default_markdown_style(true, window, cx);
let default_md_style = default_markdown_style(true, false, window, cx);
MarkdownStyle {
base_text_style: TextStyle {