Display what the tool is doing (#27120)

<img width="639" alt="Screenshot 2025-03-19 at 4 56 47 PM"
src="https://github.com/user-attachments/assets/b997f04d-4aff-4070-87b1-ffdb61019bd1"
/>

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
This commit is contained in:
Richard Feldman 2025-03-20 09:16:39 -04:00 committed by GitHub
parent aae81fd54c
commit e3578fc44a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 349 additions and 132 deletions

View file

@ -35,6 +35,7 @@ pub struct ActiveThread {
list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
rendered_scripting_tool_uses: HashMap<LanguageModelToolUseId, Entity<Markdown>>,
rendered_tool_use_labels: HashMap<LanguageModelToolUseId, Entity<Markdown>>,
editing_message: Option<(MessageId, EditMessageState)>,
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
last_error: Option<ThreadError>,
@ -70,6 +71,7 @@ impl ActiveThread {
messages: Vec::new(),
rendered_messages_by_id: HashMap::default(),
rendered_scripting_tool_uses: HashMap::default(),
rendered_tool_use_labels: HashMap::default(),
expanded_tool_uses: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade();
@ -86,10 +88,29 @@ impl ActiveThread {
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
this.push_message(&message.id, message.text.clone(), window, cx);
for tool_use in thread.read(cx).scripting_tool_uses_for_message(message.id) {
for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) {
this.render_tool_use_label_markdown(
tool_use.id.clone(),
tool_use.ui_text.clone(),
window,
cx,
);
}
for tool_use in thread
.read(cx)
.scripting_tool_uses_for_message(message.id, cx)
{
this.render_tool_use_label_markdown(
tool_use.id.clone(),
tool_use.ui_text.clone(),
window,
cx,
);
this.render_scripting_tool_use_markdown(
tool_use.id.clone(),
tool_use.name.as_ref(),
tool_use.ui_text.as_ref(),
tool_use.input.clone(),
window,
cx,
@ -287,6 +308,19 @@ impl ActiveThread {
.insert(tool_use_id, lua_script);
}
fn render_tool_use_label_markdown(
&mut self,
tool_use_id: LanguageModelToolUseId,
tool_label: impl Into<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.rendered_tool_use_labels.insert(
tool_use_id,
self.render_markdown(tool_label.into(), window, cx),
);
}
fn handle_thread_event(
&mut self,
_thread: &Entity<Thread>,
@ -341,9 +375,18 @@ impl ActiveThread {
cx.notify();
}
ThreadEvent::UsePendingTools => {
self.thread.update(cx, |thread, cx| {
thread.use_pending_tools(cx);
});
let tool_uses = self
.thread
.update(cx, |thread, cx| thread.use_pending_tools(cx));
for tool_use in tool_uses {
self.render_tool_use_label_markdown(
tool_use.id,
tool_use.ui_text.clone(),
window,
cx,
);
}
}
ThreadEvent::ToolFinished {
pending_tool_use,
@ -352,6 +395,13 @@ impl ActiveThread {
} => {
let canceled = *canceled;
if let Some(tool_use) = pending_tool_use {
self.render_tool_use_label_markdown(
tool_use.id.clone(),
SharedString::from(tool_use.ui_text.clone()),
window,
cx,
);
self.render_scripting_tool_use_markdown(
tool_use.id.clone(),
tool_use.name.as_ref(),
@ -555,8 +605,8 @@ impl ActiveThread {
// Get all the data we need from thread before we start using it in closures
let checkpoint = thread.checkpoint_for_message(message_id);
let context = thread.context_for_message(message_id);
let tool_uses = thread.tool_uses_for_message(message_id);
let scripting_tool_uses = thread.scripting_tool_uses_for_message(message_id);
let tool_uses = thread.tool_uses_for_message(message_id, cx);
let scripting_tool_uses = thread.scripting_tool_uses_for_message(message_id, cx);
// Don't render user messages that are just there for returning tool results.
if message.role == Role::User
@ -709,27 +759,25 @@ impl ActiveThread {
)
.child(div().p_2().child(message_content)),
),
Role::Assistant => {
v_flex()
.id(("message-container", ix))
.child(div().py_3().px_4().child(message_content))
.when(
!tool_uses.is_empty() || !scripting_tool_uses.is_empty(),
|parent| {
parent.child(
v_flex()
.children(
tool_uses
.into_iter()
.map(|tool_use| self.render_tool_use(tool_use, cx)),
)
.children(scripting_tool_uses.into_iter().map(|tool_use| {
self.render_scripting_tool_use(tool_use, cx)
})),
)
},
)
}
Role::Assistant => v_flex()
.id(("message-container", ix))
.child(div().py_3().px_4().child(message_content))
.when(
!tool_uses.is_empty() || !scripting_tool_uses.is_empty(),
|parent| {
parent.child(
v_flex()
.children(
tool_uses
.into_iter()
.map(|tool_use| self.render_tool_use(tool_use, cx)),
)
.children(scripting_tool_uses.into_iter().map(|tool_use| {
self.render_scripting_tool_use(tool_use, window, cx)
})),
)
},
),
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
v_flex()
.bg(colors.editor_background)
@ -805,11 +853,10 @@ impl ActiveThread {
}
}),
))
.child(
Label::new(tool_use.name)
.size(LabelSize::Small)
.buffer_font(cx),
),
.child(div().text_ui_sm(cx).children(
self.rendered_tool_use_labels.get(&tool_use.id).cloned(),
))
.truncate(),
)
.child({
let (icon_name, color, animated) = match &tool_use.status {
@ -937,6 +984,7 @@ impl ActiveThread {
fn render_scripting_tool_use(
&self,
tool_use: ToolUse,
window: &Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let is_open = self
@ -982,7 +1030,12 @@ impl ActiveThread {
}
}),
))
.child(Label::new(tool_use.name)),
.child(div().text_ui_sm(cx).child(self.render_markdown(
tool_use.ui_text.clone(),
window,
cx,
)))
.truncate(),
)
.child(
Label::new(match tool_use.status {