Render messages as early as possible to show progress (#11569)

This shows "Researching..." as placeholder text as early as possible so
that the user can see the model is working on reading/researching/etc.

This also adds on an `Option<Value>` to the `render_running` function so
that tools can hopefully render based on partially completed JSON (still
to come).

Release Notes:

- N/A
This commit is contained in:
Kyle Kelley 2024-05-08 10:24:51 -07:00 committed by GitHub
parent dbebb40956
commit 689e4aef2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 46 additions and 15 deletions

1
Cargo.lock generated
View file

@ -423,6 +423,7 @@ dependencies = [
"serde_json", "serde_json",
"settings", "settings",
"sum_tree", "sum_tree",
"ui",
"unindent", "unindent",
"util", "util",
] ]

View file

@ -16,7 +16,8 @@ use crate::{
use ::ui::{div, prelude::*, Color, Tooltip, ViewContext}; use ::ui::{div, prelude::*, Color, Tooltip, ViewContext};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use assistant_tooling::{ use assistant_tooling::{
AttachmentRegistry, ProjectContext, ToolFunctionCall, ToolRegistry, UserAttachment, tool_running_placeholder, AttachmentRegistry, ProjectContext, ToolFunctionCall, ToolRegistry,
UserAttachment,
}; };
use client::{proto, Client, UserStore}; use client::{proto, Client, UserStore};
use collections::HashMap; use collections::HashMap;
@ -864,6 +865,10 @@ impl AssistantChat {
} }
} }
if message_elements.is_empty() {
message_elements.push(tool_running_placeholder());
}
div() div()
.when(is_first, |this| this.pt(padding)) .when(is_first, |this| this.pt(padding))
.child( .child(

View file

@ -6,6 +6,7 @@ use project::ProjectPath;
use schemars::JsonSchema; use schemars::JsonSchema;
use semantic_index::{ProjectIndex, Status}; use semantic_index::{ProjectIndex, Status};
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value;
use std::{fmt::Write as _, ops::Range}; use std::{fmt::Write as _, ops::Range};
use ui::{div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, WindowContext}; use ui::{div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, WindowContext};
@ -202,8 +203,14 @@ impl LanguageModelTool for ProjectIndexTool {
cx.new_view(|_cx| ProjectIndexView::new(input, output)) cx.new_view(|_cx| ProjectIndexView::new(input, output))
} }
fn render_running(_: &mut WindowContext) -> impl IntoElement { fn render_running(arguments: &Option<Value>, _: &mut WindowContext) -> impl IntoElement {
CollapsibleContainer::new(ElementId::Name(nanoid::nanoid!().into()), false) let text: String = arguments
.start_slot("Searching code base") .as_ref()
.and_then(|arguments| arguments.get("query"))
.and_then(|query| query.as_str())
.map(|query| format!("Searching for: {}", query))
.unwrap_or_else(|| "Preparing search...".to_string());
CollapsibleContainer::new(ElementId::Name(nanoid::nanoid!().into()), false).start_slot(text)
} }
} }

View file

@ -21,6 +21,7 @@ schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
sum_tree.workspace = true sum_tree.workspace = true
ui.workspace = true
util.workspace = true util.workspace = true
[dev-dependencies] [dev-dependencies]

View file

@ -5,5 +5,6 @@ mod tool_registry;
pub use attachment_registry::{AttachmentRegistry, LanguageModelAttachment, UserAttachment}; pub use attachment_registry::{AttachmentRegistry, LanguageModelAttachment, UserAttachment};
pub use project_context::ProjectContext; pub use project_context::ProjectContext;
pub use tool_registry::{ pub use tool_registry::{
LanguageModelTool, ToolFunctionCall, ToolFunctionDefinition, ToolOutput, ToolRegistry, tool_running_placeholder, LanguageModelTool, ToolFunctionCall, ToolFunctionDefinition,
ToolOutput, ToolRegistry,
}; };

View file

@ -4,6 +4,7 @@ use gpui::{
}; };
use schemars::{schema::RootSchema, schema_for, JsonSchema}; use schemars::{schema::RootSchema, schema_for, JsonSchema};
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value;
use std::{ use std::{
any::TypeId, any::TypeId,
collections::HashMap, collections::HashMap,
@ -78,17 +79,22 @@ pub trait LanguageModelTool {
/// Executes the tool with the given input. /// Executes the tool with the given input.
fn execute(&self, input: &Self::Input, cx: &mut WindowContext) -> Task<Result<Self::Output>>; fn execute(&self, input: &Self::Input, cx: &mut WindowContext) -> Task<Result<Self::Output>>;
/// A view of the output of running the tool, for displaying to the user.
fn output_view( fn output_view(
input: Self::Input, input: Self::Input,
output: Result<Self::Output>, output: Result<Self::Output>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> View<Self::View>; ) -> View<Self::View>;
fn render_running(_cx: &mut WindowContext) -> impl IntoElement { fn render_running(_arguments: &Option<Value>, _cx: &mut WindowContext) -> impl IntoElement {
div() tool_running_placeholder()
} }
} }
pub fn tool_running_placeholder() -> AnyElement {
ui::Label::new("Researching...").into_any_element()
}
pub trait ToolOutput: Sized { pub trait ToolOutput: Sized {
fn generate(&self, project: &mut ProjectContext, cx: &mut WindowContext) -> String; fn generate(&self, project: &mut ProjectContext, cx: &mut WindowContext) -> String;
} }
@ -97,7 +103,7 @@ struct RegisteredTool {
enabled: AtomicBool, enabled: AtomicBool,
type_id: TypeId, type_id: TypeId,
call: Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>, call: Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>,
render_running: fn(&mut WindowContext) -> gpui::AnyElement, render_running: fn(&ToolFunctionCall, &mut WindowContext) -> gpui::AnyElement,
definition: ToolFunctionDefinition, definition: ToolFunctionDefinition,
} }
@ -144,11 +150,15 @@ impl ToolRegistry {
.p_2() .p_2()
.child(result.into_any_element(&tool_call.name)) .child(result.into_any_element(&tool_call.name))
.into_any_element(), .into_any_element(),
None => self None => {
.registered_tools let tool = self.registered_tools.get(&tool_call.name);
.get(&tool_call.name)
.map(|tool| (tool.render_running)(cx)) if let Some(tool) = tool {
.unwrap_or_else(|| div().into_any_element()), (tool.render_running)(&tool_call, cx)
} else {
tool_running_placeholder()
}
}
} }
} }
@ -205,8 +215,14 @@ impl ToolRegistry {
return Ok(()); return Ok(());
fn render_running<T: LanguageModelTool>(cx: &mut WindowContext) -> AnyElement { fn render_running<T: LanguageModelTool>(
T::render_running(cx).into_any_element() tool_call: &ToolFunctionCall,
cx: &mut WindowContext,
) -> AnyElement {
// Attempt to parse the string arguments that are JSON as a JSON value
let maybe_arguments = serde_json::to_value(tool_call.arguments.clone()).ok();
T::render_running(&maybe_arguments, cx).into_any_element()
} }
fn generate<T: LanguageModelTool>( fn generate<T: LanguageModelTool>(