file_interactions.rs

  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}