Trim index output (#11445)

Trims down the project index output view in assistant2 to just list the
filenames and hideaway the query.

<img width="374" alt="image"
src="https://github.com/zed-industries/zed/assets/836375/8603e3cf-9fdc-4661-bc45-1d87621a006f">

Introduces a way for tools to render running.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Kyle Kelley 2024-05-06 10:37:31 -07:00 committed by GitHub
parent f658af5903
commit 32b59bfa0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 75 deletions

View file

@ -724,21 +724,7 @@ impl AssistantChat {
let tools = tool_calls
.iter()
.map(|tool_call| {
let result = &tool_call.result;
let name = tool_call.name.clone();
match result {
Some(result) => div()
.p_2()
.child(result.into_any_element(&name))
.into_any_element(),
None => div()
.p_2()
.child(Label::new(name).color(Color::Modified))
.child("Running...")
.into_any_element(),
}
})
.map(|tool_call| self.tool_registry.render_tool_call(tool_call, cx))
.collect::<Vec<AnyElement>>();
let tools_body = if tools.is_empty() {

View file

@ -1,14 +1,12 @@
use anyhow::Result;
use assistant_tooling::{
// assistant_tool_button::{AssistantToolButton, ToolStatus},
LanguageModelTool,
};
use assistant_tooling::LanguageModelTool;
use gpui::{prelude::*, Model, Task};
use project::Fs;
use schemars::JsonSchema;
use semantic_index::{ProjectIndex, Status};
use serde::Deserialize;
use std::sync::Arc;
use std::{collections::HashSet, sync::Arc};
use ui::{
div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, SharedString,
WindowContext,
@ -22,8 +20,6 @@ pub struct CodebaseExcerpt {
path: SharedString,
text: SharedString,
score: f32,
element_id: ElementId,
expanded: bool,
}
// Note: Comments on a `LanguageModelTool::Input` become descriptions on the generated JSON schema as shown to the language model.
@ -40,21 +36,26 @@ pub struct CodebaseQuery {
pub struct ProjectIndexView {
input: CodebaseQuery,
output: Result<ProjectIndexOutput>,
element_id: ElementId,
expanded_header: bool,
}
impl ProjectIndexView {
fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext<Self>) {
if let Ok(output) = &mut self.output {
if let Some(excerpt) = output
.excerpts
.iter_mut()
.find(|excerpt| excerpt.element_id == element_id)
{
excerpt.expanded = !excerpt.expanded;
cx.notify();
}
fn new(input: CodebaseQuery, output: Result<ProjectIndexOutput>) -> Self {
let element_id = ElementId::Name(nanoid::nanoid!().into());
Self {
input,
output,
element_id,
expanded_header: false,
}
}
fn toggle_header(&mut self, cx: &mut ViewContext<Self>) {
self.expanded_header = !self.expanded_header;
cx.notify();
}
}
impl Render for ProjectIndexView {
@ -70,42 +71,47 @@ impl Render for ProjectIndexView {
Ok(output) => output,
};
div()
.v_flex()
.gap_2()
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.child(Label::new("Query: ").color(Color::Modified))
.child(Label::new(query).color(Color::Muted)),
),
)
.children(output.excerpts.iter().map(|excerpt| {
let element_id = excerpt.element_id.clone();
let expanded = excerpt.expanded;
let num_files_searched = output.files_searched.len();
CollapsibleContainer::new(element_id.clone(), expanded)
.start_slot(
h_flex()
.gap_1()
.child(Icon::new(IconName::File).color(Color::Muted))
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
)
.on_click(cx.listener(move |this, _, cx| {
this.toggle_expanded(element_id.clone(), cx);
}))
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(excerpt.text.clone()),
)
}))
let header = h_flex()
.gap_2()
.child(Icon::new(IconName::File))
.child(format!(
"Read {} {}",
num_files_searched,
if num_files_searched == 1 {
"file"
} else {
"files"
}
));
v_flex().gap_3().child(
CollapsibleContainer::new(self.element_id.clone(), self.expanded_header)
.start_slot(header)
.on_click(cx.listener(move |this, _, cx| {
this.toggle_header(cx);
}))
.child(
v_flex()
.gap_3()
.p_3()
.child(
h_flex()
.gap_2()
.child(Icon::new(IconName::MagnifyingGlass))
.child(Label::new(format!("`{}`", query)).color(Color::Muted)),
)
.child(v_flex().gap_2().children(output.files_searched.iter().map(
|path| {
h_flex()
.gap_2()
.child(Icon::new(IconName::File))
.child(Label::new(path.clone()).color(Color::Muted))
},
))),
),
)
}
}
@ -117,6 +123,7 @@ pub struct ProjectIndexTool {
pub struct ProjectIndexOutput {
excerpts: Vec<CodebaseExcerpt>,
status: Status,
files_searched: HashSet<SharedString>,
}
impl ProjectIndexTool {
@ -138,7 +145,7 @@ impl LanguageModelTool for ProjectIndexTool {
}
fn description(&self) -> String {
"Semantic search against the user's current codebase, returning excerpts related to the query by computing a dot product against embeddings of chunks and an embedding of the query".to_string()
"Semantic search against the user's current codebase, returning excerpts related to the query by computing a dot product against embeddings of code chunks in the code base and an embedding of the query.".to_string()
}
fn execute(&self, query: &Self::Input, cx: &mut WindowContext) -> Task<Result<Self::Output>> {
@ -175,8 +182,6 @@ impl LanguageModelTool for ProjectIndexTool {
}
anyhow::Ok(CodebaseExcerpt {
element_id: ElementId::Name(nanoid::nanoid!().into()),
expanded: false,
path: path.to_string_lossy().to_string().into(),
text: SharedString::from(text[start..end].to_string()),
score: result.score,
@ -184,12 +189,21 @@ impl LanguageModelTool for ProjectIndexTool {
}
});
let mut files_searched = HashSet::new();
let excerpts = futures::future::join_all(excerpts)
.await
.into_iter()
.filter_map(|result| result.log_err())
.collect();
anyhow::Ok(ProjectIndexOutput { excerpts, status })
.inspect(|excerpt| {
files_searched.insert(excerpt.path.clone());
})
.collect::<Vec<_>>();
anyhow::Ok(ProjectIndexOutput {
excerpts,
status,
files_searched,
})
})
}
@ -199,7 +213,12 @@ impl LanguageModelTool for ProjectIndexTool {
output: Result<Self::Output>,
cx: &mut WindowContext,
) -> gpui::View<Self::View> {
cx.new_view(|_cx| ProjectIndexView { input, output })
cx.new_view(|_cx| ProjectIndexView::new(input, output))
}
fn render_running(_: &mut WindowContext) -> impl IntoElement {
CollapsibleContainer::new(ElementId::Name(nanoid::nanoid!().into()), false)
.start_slot("Searching code base")
}
fn format(_input: &Self::Input, output: &Result<Self::Output>) -> String {

View file

@ -1,5 +1,5 @@
use anyhow::{anyhow, Result};
use gpui::{Task, WindowContext};
use gpui::{div, AnyElement, IntoElement as _, ParentElement, Styled, Task, WindowContext};
use std::{
any::TypeId,
collections::HashMap,
@ -15,6 +15,7 @@ pub struct Tool {
enabled: AtomicBool,
type_id: TypeId,
call: Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>,
render_running: Box<dyn Fn(&mut WindowContext) -> gpui::AnyElement>,
definition: ToolFunctionDefinition,
}
@ -22,12 +23,14 @@ impl Tool {
fn new(
type_id: TypeId,
call: Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>,
render_running: Box<dyn Fn(&mut WindowContext) -> gpui::AnyElement>,
definition: ToolFunctionDefinition,
) -> Self {
Self {
enabled: AtomicBool::new(true),
type_id,
call,
render_running,
definition,
}
}
@ -70,6 +73,24 @@ impl ToolRegistry {
.collect()
}
pub fn render_tool_call(
&self,
tool_call: &ToolFunctionCall,
cx: &mut WindowContext,
) -> AnyElement {
match &tool_call.result {
Some(result) => div()
.p_2()
.child(result.into_any_element(&tool_call.name))
.into_any_element(),
None => self
.tools
.get(&tool_call.name)
.map(|tool| (tool.render_running)(cx))
.unwrap_or_else(|| div().into_any_element()),
}
}
pub fn register<T: 'static + LanguageModelTool>(
&mut self,
tool: T,
@ -115,6 +136,7 @@ impl ToolRegistry {
})
},
),
Box::new(|cx| T::render_running(cx).into_any_element()),
definition,
);

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use gpui::{AnyElement, AnyView, IntoElement as _, Render, Task, View, WindowContext};
use gpui::{div, AnyElement, AnyView, IntoElement, Render, Task, View, WindowContext};
use schemars::{schema::RootSchema, schema_for, JsonSchema};
use serde::Deserialize;
use std::fmt::Display;
@ -104,4 +104,8 @@ pub trait LanguageModelTool {
output: Result<Self::Output>,
cx: &mut WindowContext,
) -> View<Self::View>;
fn render_running(_cx: &mut WindowContext) -> impl IntoElement {
div()
}
}