diff --git a/Cargo.lock b/Cargo.lock index 61bd393e0c..015f7888fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,6 +703,7 @@ dependencies = [ "assistant_tool", "chrono", "collections", + "component", "feature_flags", "futures 0.3.31", "gpui", @@ -711,6 +712,7 @@ dependencies = [ "itertools 0.14.0", "language", "language_model", + "linkme", "open", "project", "rand 0.8.5", diff --git a/crates/assistant_tools/Cargo.toml b/crates/assistant_tools/Cargo.toml index 245a37ef4b..be116a6534 100644 --- a/crates/assistant_tools/Cargo.toml +++ b/crates/assistant_tools/Cargo.toml @@ -16,6 +16,7 @@ anyhow.workspace = true assistant_tool.workspace = true chrono.workspace = true collections.workspace = true +component.workspace = true feature_flags.workspace = true futures.workspace = true gpui.workspace = true @@ -24,6 +25,8 @@ http_client.workspace = true itertools.workspace = true language.workspace = true language_model.workspace = true +linkme.workspace = true +open.workspace = true project.workspace = true regex.workspace = true schemars.workspace = true @@ -31,10 +34,9 @@ serde.workspace = true serde_json.workspace = true ui.workspace = true util.workspace = true -worktree.workspace = true -open = { workspace = true } web_search.workspace = true workspace-hack.workspace = true +worktree.workspace = true zed_llm_client.workspace = true [dev-dependencies] diff --git a/crates/assistant_tools/src/web_search_tool.rs b/crates/assistant_tools/src/web_search_tool.rs index 081aef27ed..39ac7bd427 100644 --- a/crates/assistant_tools/src/web_search_tool.rs +++ b/crates/assistant_tools/src/web_search_tool.rs @@ -14,7 +14,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use ui::{IconName, Tooltip, prelude::*}; use web_search::WebSearchRegistry; -use zed_llm_client::WebSearchResponse; +use zed_llm_client::{WebSearchCitation, WebSearchResponse}; #[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct WebSearchToolInput { @@ -22,6 +22,7 @@ pub struct WebSearchToolInput { query: String, } +#[derive(RegisterComponent)] pub struct WebSearchTool; impl Tool for WebSearchTool { @@ -211,3 +212,111 @@ impl ToolCard for WebSearchToolCard { v_flex().my_2().gap_1().child(header).children(content) } } + +impl Component for WebSearchTool { + fn scope() -> ComponentScope { + ComponentScope::Agent + } + + fn sort_name() -> &'static str { + "ToolWebSearch" + } + + fn preview(window: &mut Window, cx: &mut App) -> Option { + let in_progress_search = cx.new(|cx| WebSearchToolCard { + response: None, + _task: cx.spawn(async move |_this, cx| { + loop { + cx.background_executor() + .timer(Duration::from_secs(60)) + .await + } + }), + }); + + let successful_search = cx.new(|_cx| WebSearchToolCard { + response: Some(Ok(example_search_response())), + _task: Task::ready(()), + }); + + let error_search = cx.new(|_cx| WebSearchToolCard { + response: Some(Err(anyhow!("Failed to resolve https://google.com"))), + _task: Task::ready(()), + }); + + Some( + v_flex() + .gap_6() + .children(vec![example_group(vec![ + single_example( + "In Progress", + div() + .size_full() + .child(in_progress_search.update(cx, |tool, cx| { + tool.render(&ToolUseStatus::Pending, window, cx) + .into_any_element() + })) + .into_any_element(), + ), + single_example( + "Successful", + div() + .size_full() + .child(successful_search.update(cx, |tool, cx| { + tool.render(&ToolUseStatus::Finished("".into()), window, cx) + .into_any_element() + })) + .into_any_element(), + ), + single_example( + "Error", + div() + .size_full() + .child(error_search.update(cx, |tool, cx| { + tool.render(&ToolUseStatus::Error("".into()), window, cx) + .into_any_element() + })) + .into_any_element(), + ), + ])]) + .into_any_element(), + ) + } +} + +fn example_search_response() -> WebSearchResponse { + WebSearchResponse { + summary: r#"Toronto boasts a vibrant culinary scene with a diverse array of..."# + .to_string(), + citations: vec![ + WebSearchCitation { + title: "Alo".to_string(), + url: "https://www.google.com/maps/search/Alo%2C+Toronto%2C+Canada".to_string(), + range: Some(147..213), + }, + WebSearchCitation { + title: "Edulis".to_string(), + url: "https://www.google.com/maps/search/Edulis%2C+Toronto%2C+Canada".to_string(), + range: Some(447..519), + }, + WebSearchCitation { + title: "Sushi Masaki Saito".to_string(), + url: "https://www.google.com/maps/search/Sushi+Masaki+Saito%2C+Toronto%2C+Canada" + .to_string(), + range: Some(776..872), + }, + WebSearchCitation { + title: "Shoushin".to_string(), + url: "https://www.google.com/maps/search/Shoushin%2C+Toronto%2C+Canada".to_string(), + range: Some(1072..1148), + }, + WebSearchCitation { + title: "Restaurant 20 Victoria".to_string(), + url: + "https://www.google.com/maps/search/Restaurant+20+Victoria%2C+Toronto%2C+Canada" + .to_string(), + range: Some(1291..1395), + }, + ], + } +} diff --git a/crates/component/src/component.rs b/crates/component/src/component.rs index db847d5538..8394c91b98 100644 --- a/crates/component/src/component.rs +++ b/crates/component/src/component.rs @@ -201,6 +201,7 @@ pub fn components() -> AllComponents { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ComponentScope { + Agent, Collaboration, DataDisplay, Editor, @@ -220,6 +221,7 @@ pub enum ComponentScope { impl Display for ComponentScope { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + ComponentScope::Agent => write!(f, "Agent"), ComponentScope::Collaboration => write!(f, "Collaboration"), ComponentScope::DataDisplay => write!(f, "Data Display"), ComponentScope::Editor => write!(f, "Editor"), diff --git a/typos.toml b/typos.toml index 1c90bf5926..72bc3e8ccf 100644 --- a/typos.toml +++ b/typos.toml @@ -19,6 +19,9 @@ extend-exclude = [ # Some crate names are flagged as typos. "crates/indexed_docs/src/providers/rustdoc/popular_crates.txt", + # Some mock data is flagged as typos. + "crates/assistant_tools/src/web_search_tool.rs", + # Stripe IDs are flagged as typos. "crates/collab/src/db/tests/processed_stripe_event_tests.rs", # Not our typos.