assistant2: Visually de-emphasize read-only tool calls (#27702)
<img src="https://github.com/user-attachments/assets/03961518-ae40-47d8-b84c-974c9b897eb3" width="500"/> Release Notes: - N/A --------- Co-authored-by: Agus Zubiaga <hi@aguz.me>
This commit is contained in:
parent
d63658cee2
commit
044508ef77
2 changed files with 272 additions and 191 deletions
|
@ -1401,209 +1401,287 @@ impl ActiveThread {
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
div().py_2().child(
|
let status_icons = div().child({
|
||||||
v_flex()
|
let (icon_name, color, animated) = match &tool_use.status {
|
||||||
.rounded_lg()
|
ToolUseStatus::Pending | ToolUseStatus::NeedsConfirmation => {
|
||||||
.border_1()
|
(IconName::Warning, Color::Warning, false)
|
||||||
.border_color(self.tool_card_border_color(cx))
|
}
|
||||||
.overflow_hidden()
|
ToolUseStatus::Running => (IconName::ArrowCircle, Color::Accent, true),
|
||||||
.child(
|
ToolUseStatus::Finished(_) => (IconName::Check, Color::Success, false),
|
||||||
h_flex()
|
ToolUseStatus::Error(_) => (IconName::Close, Color::Error, false),
|
||||||
.group("disclosure-header")
|
};
|
||||||
.relative()
|
|
||||||
.gap_1p5()
|
let icon = Icon::new(icon_name).color(color).size(IconSize::Small);
|
||||||
.justify_between()
|
|
||||||
.py_1()
|
if animated {
|
||||||
.px_2()
|
icon.with_animation(
|
||||||
.bg(self.tool_card_header_bg(cx))
|
"arrow-circle",
|
||||||
.map(|element| {
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
if is_open {
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
element.border_b_1().rounded_t_md()
|
)
|
||||||
} else {
|
.into_any_element()
|
||||||
element.rounded_md()
|
} else {
|
||||||
}
|
icon.into_any_element()
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let content_container = || v_flex().py_1().gap_0p5().px_2p5();
|
||||||
|
let results_content = v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
content_container()
|
||||||
|
.child(
|
||||||
|
Label::new("Input")
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(
|
||||||
|
serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.buffer_font(cx),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|container| match tool_use.status {
|
||||||
|
ToolUseStatus::Finished(output) => container.child(
|
||||||
|
content_container()
|
||||||
|
.border_t_1()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
Label::new("Result")
|
||||||
.id("tool-label-container")
|
.size(LabelSize::XSmall)
|
||||||
.relative()
|
.color(Color::Muted)
|
||||||
.gap_1p5()
|
.buffer_font(cx),
|
||||||
.max_w_full()
|
|
||||||
.overflow_x_scroll()
|
|
||||||
.child(
|
|
||||||
Icon::new(tool_use.icon)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(h_flex().pr_8().text_ui_sm(cx).children(
|
|
||||||
self.rendered_tool_use_labels.get(&tool_use.id).cloned(),
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(Label::new(output).size(LabelSize::Small).buffer_font(cx)),
|
||||||
h_flex()
|
),
|
||||||
.gap_1()
|
ToolUseStatus::Running => container.child(
|
||||||
.child(
|
content_container().child(
|
||||||
div().visible_on_hover("disclosure-header").child(
|
h_flex()
|
||||||
Disclosure::new("tool-use-disclosure", is_open)
|
|
||||||
.opened_icon(IconName::ChevronUp)
|
|
||||||
.closed_icon(IconName::ChevronDown)
|
|
||||||
.on_click(cx.listener({
|
|
||||||
let tool_use_id = tool_use.id.clone();
|
|
||||||
move |this, _event, _window, _cx| {
|
|
||||||
let is_open = this
|
|
||||||
.expanded_tool_uses
|
|
||||||
.entry(tool_use_id.clone())
|
|
||||||
.or_insert(false);
|
|
||||||
|
|
||||||
*is_open = !*is_open;
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child({
|
|
||||||
let (icon_name, color, animated) = match &tool_use.status {
|
|
||||||
ToolUseStatus::Pending
|
|
||||||
| ToolUseStatus::NeedsConfirmation => {
|
|
||||||
(IconName::Warning, Color::Warning, false)
|
|
||||||
}
|
|
||||||
ToolUseStatus::Running => {
|
|
||||||
(IconName::ArrowCircle, Color::Accent, true)
|
|
||||||
}
|
|
||||||
ToolUseStatus::Finished(_) => {
|
|
||||||
(IconName::Check, Color::Success, false)
|
|
||||||
}
|
|
||||||
ToolUseStatus::Error(_) => {
|
|
||||||
(IconName::Close, Color::Error, false)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let icon =
|
|
||||||
Icon::new(icon_name).color(color).size(IconSize::Small);
|
|
||||||
|
|
||||||
if animated {
|
|
||||||
icon.with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
|
||||||
|icon, delta| {
|
|
||||||
icon.transform(Transformation::rotate(percentage(
|
|
||||||
delta,
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
} else {
|
|
||||||
icon.into_any_element()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(div().h_full().absolute().w_8().bottom_0().right_12().bg(
|
|
||||||
linear_gradient(
|
|
||||||
90.,
|
|
||||||
linear_color_stop(self.tool_card_header_bg(cx), 1.),
|
|
||||||
linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.map(|parent| {
|
|
||||||
if !is_open {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
let content_container = || v_flex().py_1().gap_0p5().px_2p5();
|
|
||||||
|
|
||||||
parent.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.pb_1()
|
||||||
.rounded_b_lg()
|
.border_t_1()
|
||||||
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.child(
|
.child(
|
||||||
content_container()
|
Icon::new(IconName::ArrowCircle)
|
||||||
.border_b_1()
|
.size(IconSize::Small)
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.color(Color::Accent)
|
||||||
.child(
|
.with_animation(
|
||||||
Label::new("Input")
|
"arrow-circle",
|
||||||
.size(LabelSize::XSmall)
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
.color(Color::Muted)
|
|icon, delta| {
|
||||||
.buffer_font(cx),
|
icon.transform(Transformation::rotate(percentage(
|
||||||
)
|
delta,
|
||||||
.child(
|
)))
|
||||||
Label::new(
|
},
|
||||||
serde_json::to_string_pretty(&tool_use.input)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.buffer_font(cx),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.map(|container| match tool_use.status {
|
.child(
|
||||||
ToolUseStatus::Finished(output) => container.child(
|
Label::new("Running…")
|
||||||
content_container()
|
.size(LabelSize::XSmall)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.buffer_font(cx),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ToolUseStatus::Error(err) => container.child(
|
||||||
|
content_container()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(self.tool_card_border_color(cx))
|
||||||
|
.child(
|
||||||
|
Label::new("Error")
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
.child(Label::new(err).size(LabelSize::Small).buffer_font(cx)),
|
||||||
|
),
|
||||||
|
ToolUseStatus::Pending => container,
|
||||||
|
ToolUseStatus::NeedsConfirmation => container.child(
|
||||||
|
content_container()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(self.tool_card_border_color(cx))
|
||||||
|
.child(
|
||||||
|
Label::new("Asking Permission")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.buffer_font(cx),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
fn gradient_overlay(color: Hsla) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.h_full()
|
||||||
|
.absolute()
|
||||||
|
.w_8()
|
||||||
|
.bottom_0()
|
||||||
|
.right_12()
|
||||||
|
.bg(linear_gradient(
|
||||||
|
90.,
|
||||||
|
linear_color_stop(color, 1.),
|
||||||
|
linear_color_stop(color.opacity(0.2), 0.),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
div().map(|this| {
|
||||||
|
if !tool_use.needs_confirmation {
|
||||||
|
this.py_2p5().child(
|
||||||
|
v_flex()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.group("disclosure-header")
|
||||||
|
.relative()
|
||||||
|
.gap_1p5()
|
||||||
|
.justify_between()
|
||||||
|
.opacity(0.8)
|
||||||
|
.hover(|style| style.opacity(1.))
|
||||||
|
.pr_2()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.id("tool-label-container")
|
||||||
|
.gap_1p5()
|
||||||
|
.max_w_full()
|
||||||
|
.overflow_x_scroll()
|
||||||
.child(
|
.child(
|
||||||
Label::new("Result")
|
Icon::new(tool_use.icon)
|
||||||
.size(LabelSize::XSmall)
|
.size(IconSize::XSmall)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted),
|
||||||
.buffer_font(cx),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Label::new(output)
|
h_flex().pr_8().text_ui_sm(cx).children(
|
||||||
.size(LabelSize::Small)
|
self.rendered_tool_use_labels
|
||||||
.buffer_font(cx),
|
.get(&tool_use.id)
|
||||||
),
|
.cloned(),
|
||||||
),
|
|
||||||
ToolUseStatus::Running => container.child(
|
|
||||||
content_container().child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.pb_1()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ArrowCircle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Accent)
|
|
||||||
.with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(2))
|
|
||||||
.repeat(),
|
|
||||||
|icon, delta| {
|
|
||||||
icon.transform(Transformation::rotate(
|
|
||||||
percentage(delta),
|
|
||||||
))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new("Running…")
|
|
||||||
.size(LabelSize::XSmall)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.buffer_font(cx),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
ToolUseStatus::Error(err) => container.child(
|
.child(
|
||||||
content_container()
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
Label::new("Error")
|
div().visible_on_hover("disclosure-header").child(
|
||||||
.size(LabelSize::XSmall)
|
Disclosure::new("tool-use-disclosure", is_open)
|
||||||
.color(Color::Muted)
|
.opened_icon(IconName::ChevronUp)
|
||||||
.buffer_font(cx),
|
.closed_icon(IconName::ChevronDown)
|
||||||
|
.on_click(cx.listener({
|
||||||
|
let tool_use_id = tool_use.id.clone();
|
||||||
|
move |this, _event, _window, _cx| {
|
||||||
|
let is_open = this
|
||||||
|
.expanded_tool_uses
|
||||||
|
.entry(tool_use_id.clone())
|
||||||
|
.or_insert(false);
|
||||||
|
|
||||||
|
*is_open = !*is_open;
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(status_icons),
|
||||||
|
)
|
||||||
|
.child(gradient_overlay(cx.theme().colors().panel_background)),
|
||||||
|
)
|
||||||
|
.map(|parent| {
|
||||||
|
if !is_open {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.child(
|
||||||
|
v_flex()
|
||||||
|
.mt_1()
|
||||||
|
.border_1()
|
||||||
|
.border_color(self.tool_card_border_color(cx))
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.rounded_lg()
|
||||||
|
.child(results_content),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.py_2().child(
|
||||||
|
v_flex()
|
||||||
|
.rounded_lg()
|
||||||
|
.border_1()
|
||||||
|
.border_color(self.tool_card_border_color(cx))
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.group("disclosure-header")
|
||||||
|
.relative()
|
||||||
|
.gap_1p5()
|
||||||
|
.justify_between()
|
||||||
|
.py_1()
|
||||||
|
.px_2()
|
||||||
|
.bg(self.tool_card_header_bg(cx))
|
||||||
|
.map(|element| {
|
||||||
|
if is_open {
|
||||||
|
element.border_b_1().rounded_t_md()
|
||||||
|
} else {
|
||||||
|
element.rounded_md()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.border_color(self.tool_card_border_color(cx))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.id("tool-label-container")
|
||||||
|
.gap_1p5()
|
||||||
|
.max_w_full()
|
||||||
|
.overflow_x_scroll()
|
||||||
|
.child(
|
||||||
|
Icon::new(tool_use.icon)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Label::new(err).size(LabelSize::Small).buffer_font(cx),
|
h_flex().pr_8().text_ui_sm(cx).children(
|
||||||
|
self.rendered_tool_use_labels
|
||||||
|
.get(&tool_use.id)
|
||||||
|
.cloned(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
ToolUseStatus::Pending => container,
|
.child(
|
||||||
ToolUseStatus::NeedsConfirmation => container.child(
|
h_flex()
|
||||||
content_container().child(
|
.gap_1()
|
||||||
Label::new("Asking Permission")
|
.child(
|
||||||
.size(LabelSize::Small)
|
div().visible_on_hover("disclosure-header").child(
|
||||||
.color(Color::Muted)
|
Disclosure::new("tool-use-disclosure", is_open)
|
||||||
.buffer_font(cx),
|
.opened_icon(IconName::ChevronUp)
|
||||||
),
|
.closed_icon(IconName::ChevronDown)
|
||||||
),
|
.on_click(cx.listener({
|
||||||
}),
|
let tool_use_id = tool_use.id.clone();
|
||||||
)
|
move |this, _event, _window, _cx| {
|
||||||
}),
|
let is_open = this
|
||||||
)
|
.expanded_tool_uses
|
||||||
|
.entry(tool_use_id.clone())
|
||||||
|
.or_insert(false);
|
||||||
|
|
||||||
|
*is_open = !*is_open;
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(status_icons),
|
||||||
|
)
|
||||||
|
.child(gradient_overlay(self.tool_card_header_bg(cx))),
|
||||||
|
)
|
||||||
|
.map(|parent| {
|
||||||
|
if !is_open {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.child(
|
||||||
|
v_flex()
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.rounded_b_lg()
|
||||||
|
.child(results_content),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
|
fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub struct ToolUse {
|
||||||
pub status: ToolUseStatus,
|
pub status: ToolUseStatus,
|
||||||
pub input: serde_json::Value,
|
pub input: serde_json::Value,
|
||||||
pub icon: ui::IconName,
|
pub icon: ui::IconName,
|
||||||
|
pub needs_confirmation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -181,10 +182,11 @@ impl ToolUseState {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let icon = if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
let (icon, needs_confirmation) = if let Some(tool) = self.tools.tool(&tool_use.name, cx)
|
||||||
tool.icon()
|
{
|
||||||
|
(tool.icon(), tool.needs_confirmation())
|
||||||
} else {
|
} else {
|
||||||
IconName::Cog
|
(IconName::Cog, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
tool_uses.push(ToolUse {
|
tool_uses.push(ToolUse {
|
||||||
|
@ -194,6 +196,7 @@ impl ToolUseState {
|
||||||
input: tool_use.input.clone(),
|
input: tool_use.input.clone(),
|
||||||
status,
|
status,
|
||||||
icon,
|
icon,
|
||||||
|
needs_confirmation,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue