1//! This example creates a basic Chat UI for interacting with the filesystem.
2
3use anyhow::{Context as _, Result};
4use assets::Assets;
5use assistant2::AssistantPanel;
6use assistant_tooling::{LanguageModelTool, ToolRegistry};
7use client::Client;
8use fs::Fs;
9use futures::StreamExt;
10use gpui::{actions, App, AppContext, KeyBinding, Task, View, WindowOptions};
11use language::LanguageRegistry;
12use project::Project;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
16use std::path::PathBuf;
17use std::sync::Arc;
18use theme::LoadThemes;
19use ui::{div, prelude::*, Render};
20use util::ResultExt as _;
21
22actions!(example, [Quit]);
23
24struct FileBrowserTool {
25 fs: Arc<dyn Fs>,
26 root_dir: PathBuf,
27}
28
29impl FileBrowserTool {
30 fn new(fs: Arc<dyn Fs>, root_dir: PathBuf) -> Self {
31 Self { fs, root_dir }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
36struct FileBrowserParams {
37 command: FileBrowserCommand,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41enum FileBrowserCommand {
42 Ls { path: PathBuf },
43 Cat { path: PathBuf },
44}
45
46#[derive(Serialize, Deserialize)]
47enum FileBrowserOutput {
48 Ls { entries: Vec<String> },
49 Cat { content: String },
50}
51
52pub struct FileBrowserView {
53 result: Result<FileBrowserOutput>,
54}
55
56impl Render for FileBrowserView {
57 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
58 let Ok(output) = self.result.as_ref() else {
59 return h_flex().child("Failed to perform operation");
60 };
61
62 match output {
63 FileBrowserOutput::Ls { entries } => v_flex().children(
64 entries
65 .into_iter()
66 .map(|entry| h_flex().text_ui(cx).child(entry.clone())),
67 ),
68 FileBrowserOutput::Cat { content } => h_flex().child(content.clone()),
69 }
70 }
71}
72
73impl LanguageModelTool for FileBrowserTool {
74 type Input = FileBrowserParams;
75 type Output = FileBrowserOutput;
76 type View = FileBrowserView;
77
78 fn name(&self) -> String {
79 "file_browser".to_string()
80 }
81
82 fn description(&self) -> String {
83 "A tool for browsing the filesystem.".to_string()
84 }
85
86 fn execute(&self, input: &Self::Input, cx: &AppContext) -> Task<gpui::Result<Self::Output>> {
87 cx.spawn({
88 let fs = self.fs.clone();
89 let root_dir = self.root_dir.clone();
90 let input = input.clone();
91 |_cx| async move {
92 match input.command {
93 FileBrowserCommand::Ls { path } => {
94 let path = root_dir.join(path);
95
96 let mut output = fs.read_dir(&path).await?;
97
98 let mut entries = Vec::new();
99 while let Some(entry) = output.next().await {
100 let entry = entry?;
101 entries.push(entry.display().to_string());
102 }
103
104 Ok(FileBrowserOutput::Ls { entries })
105 }
106 FileBrowserCommand::Cat { path } => {
107 let path = root_dir.join(path);
108
109 let output = fs.load(&path).await?;
110
111 Ok(FileBrowserOutput::Cat { content: output })
112 }
113 }
114 }
115 })
116 }
117
118 fn output_view(
119 _tool_call_id: String,
120 _input: Self::Input,
121 result: Result<Self::Output>,
122 cx: &mut WindowContext,
123 ) -> gpui::View<Self::View> {
124 cx.new_view(|_cx| FileBrowserView { result })
125 }
126
127 fn format(_input: &Self::Input, output: &Result<Self::Output>) -> String {
128 let Ok(output) = output else {
129 return "Failed to perform command: {input:?}".to_string();
130 };
131
132 match output {
133 FileBrowserOutput::Ls { entries } => entries.join("\n"),
134 FileBrowserOutput::Cat { content } => content.to_owned(),
135 }
136 }
137}
138
139fn main() {
140 env_logger::init();
141 App::new().with_assets(Assets).run(|cx| {
142 cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
143 cx.on_action(|_: &Quit, cx: &mut AppContext| {
144 cx.quit();
145 });
146
147 settings::init(cx);
148 language::init(cx);
149 Project::init_settings(cx);
150 editor::init(cx);
151 theme::init(LoadThemes::JustBase, cx);
152 Assets.load_fonts(cx).unwrap();
153 KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
154 client::init_settings(cx);
155 release_channel::init("0.130.0", cx);
156
157 let client = Client::production(cx);
158 {
159 let client = client.clone();
160 cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
161 .detach_and_log_err(cx);
162 }
163 assistant2::init(client.clone(), cx);
164
165 let language_registry = Arc::new(LanguageRegistry::new(
166 Task::ready(()),
167 cx.background_executor().clone(),
168 ));
169 let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
170 languages::init(language_registry.clone(), node_runtime, cx);
171
172 cx.spawn(|cx| async move {
173 cx.update(|cx| {
174 let fs = Arc::new(fs::RealFs::new(None));
175 let cwd = std::env::current_dir().expect("Failed to get current working directory");
176
177 cx.open_window(WindowOptions::default(), |cx| {
178 let mut tool_registry = ToolRegistry::new();
179 tool_registry
180 .register(FileBrowserTool::new(fs, cwd), cx)
181 .context("failed to register FileBrowserTool")
182 .log_err();
183
184 let tool_registry = Arc::new(tool_registry);
185
186 println!("Tools registered");
187 for definition in tool_registry.definitions() {
188 println!("{}", definition);
189 }
190
191 cx.new_view(|cx| Example::new(language_registry, tool_registry, cx))
192 });
193 cx.activate(true);
194 })
195 })
196 .detach_and_log_err(cx);
197 })
198}
199
200struct Example {
201 assistant_panel: View<AssistantPanel>,
202}
203
204impl Example {
205 fn new(
206 language_registry: Arc<LanguageRegistry>,
207 tool_registry: Arc<ToolRegistry>,
208 cx: &mut ViewContext<Self>,
209 ) -> Self {
210 Self {
211 assistant_panel: cx
212 .new_view(|cx| AssistantPanel::new(language_registry, tool_registry, cx)),
213 }
214 }
215}
216
217impl Render for Example {
218 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl ui::prelude::IntoElement {
219 div().size_full().child(self.assistant_panel.clone())
220 }
221}