From a17ec0fce193cd426f1cdab1c3a806fd924310ea Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sun, 8 Dec 2024 21:01:57 -0800 Subject: [PATCH 01/15] feat: add basic LLM capabilities --- src/cause.rs | 6 ++++- src/error.rs | 17 +++++++++++++ src/exec.rs | 68 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/cause.rs b/src/cause.rs index 0a5ab104db..9b3e636e46 100644 --- a/src/cause.rs +++ b/src/cause.rs @@ -14,7 +14,11 @@ pub struct Cause { impl IntoResponse for Cause { fn into_response(self) -> Response { - Response::new(Body::from(serde_json::to_string(&self).unwrap())) + Response::builder() + .status(400) + .header("Content-Type", "application/json") + .body(Body::from(serde_json::to_string(&self).unwrap())) + .unwrap() } } diff --git a/src/error.rs b/src/error.rs index a0d6b09b3c..d84e135109 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ use axum::response::{IntoResponse, Response}; use derive_more::derive::{Debug, From}; +use rig::completion::PromptError; use crate::cause::Cause; @@ -9,6 +10,15 @@ pub enum Error { error: tokio::io::Error, resource: String, }, + Axum { + error: axum::Error, + }, + Serde { + error: serde_json::Error, + }, + Prompt { + error: PromptError, + }, } pub type Result = std::result::Result; @@ -19,6 +29,13 @@ impl From for Cause { Error::IO { error, resource } => { Cause::new(format!("IO Error: {}", resource)).cause(Cause::new(error.to_string())) } + Error::Axum { error } => Cause::new("Axum Error").cause(Cause::new(error.to_string())), + Error::Serde { error } => { + Cause::new("Serde Error").cause(Cause::new(error.to_string())) + } + Error::Prompt { error } => { + Cause::new("Prompt Error").cause(Cause::new(error.to_string())) + } } } } diff --git a/src/exec.rs b/src/exec.rs index dcb3a9cfd1..3a5f87c1ef 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,10 +1,15 @@ - use crate::error::Result; +use axum::body::{to_bytes, Body}; +use axum::http::HeaderValue; use axum::{ - body::Body, http::{Request, Response}, + response::IntoResponse, }; use derive_more::Debug; +use rig::agent::Agent; +use rig::completion::Prompt; +use rig::providers::openai::{self, CompletionModel}; +use serde::{Deserialize, Serialize}; #[derive(Clone)] pub struct Exec {} @@ -15,32 +20,55 @@ impl Exec { } pub async fn execute(&self, request: Request) -> Result> { - println!("{:?}", request); - todo!() + let (_, body) = request.into_parts(); + let bytes = to_bytes(body, usize::MAX).await?; + let prompt = serde_json::from_slice(bytes.as_ref())?; + let action = LLMAgent::default().execute(prompt).await?; + let mut response = Response::new(Body::from(serde_json::to_vec(&action)?).into()); + response + .headers_mut() + .append("Content-Type", HeaderValue::from_static("application/json")); + Ok(response) } } -struct LLMAgent {} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +enum Action { + Prompt(String), +} -#[derive(Debug)] -enum Prompt { - Message(String), +#[derive(Debug, Serialize)] +enum Command { + Submit(String), } -impl LLMAgent { - async fn execute(prompt: Prompt) { - use rig::{completion::Prompt, providers::openai}; +impl IntoResponse for Command { + fn into_response(self) -> axum::response::Response { + match serde_json::to_vec(&self) { + Ok(body) => axum::http::Response::new(Body::from(body)).into_response(), + Err(_) => todo!(), + } + } +} - // Create OpenAI client and model - // This requires the `OPENAI_API_KEY` environment variable to be set. - let gpt4 = openai::Client::from_env().agent("gpt-4").build(); +struct LLMAgent { + agent: Agent, +} - // Prompt the model and print its response - let response = gpt4 - .prompt(format!("{:?}", prompt).as_str()) - .await - .expect("Failed to prompt GPT-4"); +impl LLMAgent { + fn default() -> Self { + // A place to initialize our LLM agent + let agent = openai::Client::from_env().agent("gpt-4o-mini").build(); + LLMAgent { agent } + } - println!("GPT-4: {response}"); + async fn execute(&self, action: Action) -> Result { + match action { + Action::Prompt(message) => { + let response = self.agent.prompt(message.as_str()).await?; + Ok(Command::Submit(response)) + } + } } } From f38d5ccd53c5a65bd9a72f7190a4c8473445d4ef Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 12 Dec 2024 20:43:31 -0800 Subject: [PATCH 02/15] --wip-- [skip ci] --- Cargo.lock | 129 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + example/.spec/todo-app.md | 29 +++++++++ src/exec.rs | 8 +++ src/prompts.rs | 1 + src/prompts/initialize.md | 3 + 6 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 example/.spec/todo-app.md create mode 100644 src/prompts.rs create mode 100644 src/prompts/initialize.md diff --git a/Cargo.lock b/Cargo.lock index 802035d030..ef459e3d5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.83" @@ -110,6 +116,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bitflags" version = "1.3.2" @@ -156,6 +168,7 @@ dependencies = [ "axum", "derive_more", "derive_setters", + "ramhorns", "rig-core", "serde", "serde_json", @@ -287,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -452,6 +465,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "0.2.12" @@ -775,6 +794,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.167" @@ -799,6 +824,40 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "logos" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6b6e02facda28ca5fb8dbe4b152496ba3b1bd5a4b40bb2b1b2d8ad74e0f39b" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3303189202bb8a052bcd93d66b6c03e6fe70d9c7c47c0ea5e974955e54c876" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774a1c225576486e4fdf40b74646f672c542ca3608160d348749693ae9d456e6" +dependencies = [ + "logos-codegen", +] + [[package]] name = "matchit" version = "0.7.3" @@ -974,6 +1033,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" +dependencies = [ + "bitflags 2.6.0", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + [[package]] name = "quote" version = "1.0.37" @@ -983,6 +1060,39 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ramhorns" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d64e28e1069d26c22d4f085ca7f9e2e2eaee16f9d6f80e5fdce62cbf8670f" +dependencies = [ + "arrayvec", + "beef", + "fnv", + "logos", + "pulldown-cmark", + "ramhorns-derive", +] + +[[package]] +name = "ramhorns-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55c1eb278808d0120a6dd2109f39a8da8fd279fc9bfcd5511e9b13e41dfcae9" +dependencies = [ + "fnv", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "reqwest" version = "0.11.27" @@ -1046,6 +1156,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.41" @@ -1136,6 +1255,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.215" @@ -1309,7 +1434,7 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7510922d5d..2f4b16d4e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" axum = "0.7.9" derive_more = { version = "1.0.0", features = ["debug", "from"] } derive_setters = "0.1.6" +ramhorns = "1.0.1" rig-core = "0.5.0" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" diff --git a/example/.spec/todo-app.md b/example/.spec/todo-app.md new file mode 100644 index 0000000000..8e6c560144 --- /dev/null +++ b/example/.spec/todo-app.md @@ -0,0 +1,29 @@ +--- +name: todo-app +--- + +# Specifications + +- Create a simple TODO application and expose REST APIs to interact with it. +- I should be able to create a TODO item, update it, delete it, and list all the TODO items. +- The List API should support pagination. +- I should be able to filter the TODO items based on the `done` field. +- I should be able to undo and redo the last action. +- It should be able to mark a TODO item as done. +- API Endpoints: + - `POST /todos` - Create a TODO item + - `GET /todos` - List all TODO items (pagination supported) + - `GET /todos/{id}` - Get a TODO item by ID + - `PUT /todos/{id}` - Update a TODO item by ID + - `DELETE /todos/{id}` - Delete a TODO item by ID + - `POST /todos/{id}/done` - Mark a TODO item as done + - `POST /todos/undo` - Undo the last action + - `POST /todos/redo` - Redo the last action + - `GET /todos/history` - Get the history of actions (pagination supported) + +# Technical Requirements + +- It should be written in Rust +- Use Axum as the web framework +- The backend should be written in surrealDB +- Add unit tests for the backend diff --git a/src/exec.rs b/src/exec.rs index 3a5f87c1ef..fd572d51a8 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -38,6 +38,14 @@ enum Action { Prompt(String), } +impl Action { + async fn from_request(request: Request) -> Result { + let (_, body) = request.into_parts(); + let bytes = to_bytes(body, usize::MAX).await?; + Ok(serde_json::from_slice(bytes.as_ref())?) + } +} + #[derive(Debug, Serialize)] enum Command { Submit(String), diff --git a/src/prompts.rs b/src/prompts.rs new file mode 100644 index 0000000000..f18b68dd4c --- /dev/null +++ b/src/prompts.rs @@ -0,0 +1 @@ +pub const INITIALIZE: &str = include_str!("./prompts/initialize.md"); diff --git a/src/prompts/initialize.md b/src/prompts/initialize.md new file mode 100644 index 0000000000..64686a2e20 --- /dev/null +++ b/src/prompts/initialize.md @@ -0,0 +1,3 @@ +Understand the product specification below and create an high-level plan to implement the product. + +{{specification}} From 96374d820cd9befb10eab72a9af4cfa4943d4547 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 12 Dec 2024 20:59:10 -0800 Subject: [PATCH 03/15] chore: reinitialize the project --- Cargo.lock | 1932 +++---------------------------------- Cargo.toml | 12 +- assets/index.html | 62 -- example/.spec/todo-app.md | 29 - src/api.rs | 25 - src/cause.rs | 32 - src/error.rs | 47 - src/exec.rs | 82 -- src/main.rs | 18 +- src/prompts.rs | 1 - src/prompts/initialize.md | 3 - 11 files changed, 155 insertions(+), 2088 deletions(-) delete mode 100644 assets/index.html delete mode 100644 example/.spec/todo-app.md delete mode 100644 src/api.rs delete mode 100644 src/cause.rs delete mode 100644 src/error.rs delete mode 100644 src/exec.rs delete mode 100644 src/prompts.rs delete mode 100644 src/prompts/initialize.md diff --git a/Cargo.lock b/Cargo.lock index ef459e3d5c..e029504ecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,84 +17,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.5.1", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -107,54 +35,21 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" -[[package]] -name = "cc" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -165,1821 +60,298 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "code-forge" version = "0.1.0" dependencies = [ - "axum", - "derive_more", - "derive_setters", - "ramhorns", - "rig-core", - "serde", - "serde_json", "tokio", - "tower", - "tower-http", + "tokio-stream", ] [[package]] -name = "core-foundation" -version = "0.9.4" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "darling" -version = "0.20.10" +name = "libc" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] -name = "darling_core" -version = "0.20.10" +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", + "autocfg", + "scopeguard", ] [[package]] -name = "darling_macro" -version = "0.20.10" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "derive_more" -version = "1.0.0" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "derive_more-impl", + "adler2", ] [[package]] -name = "derive_more-impl" -version = "1.0.0" +name = "mio" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", + "libc", + "wasi", + "windows-sys", ] [[package]] -name = "derive_setters" -version = "0.1.6" +name = "object" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", + "memchr", ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "parking_lot" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ - "proc-macro2", - "quote", - "syn", + "lock_api", + "parking_lot_core", ] [[package]] -name = "dyn-clone" -version = "1.0.17" +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "pin-project-lite" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] -name = "errno" -version = "0.3.10" +name = "proc-macro2" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ - "libc", - "windows-sys 0.59.0", + "unicode-ident", ] [[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "foreign-types-shared", + "proc-macro2", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "redox_syscall" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] [[package]] -name = "form_urlencoded" -version = "1.2.1" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "futures" -version = "0.3.31" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "futures-channel" -version = "0.3.31" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ - "futures-core", - "futures-sink", + "libc", ] [[package]] -name = "futures-core" -version = "0.3.31" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "futures-executor" -version = "0.3.31" +name = "socket2" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "libc", + "windows-sys", ] [[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" +name = "syn" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" +name = "tokio" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", "pin-project-lite", - "pin-utils", - "slab", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", ] [[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.1" +name = "tokio-macros" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "h2" -version = "0.3.26" +name = "tokio-stream" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ - "bytes", - "fnv", "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", + "pin-project-lite", "tokio", - "tokio-util", - "tracing", ] [[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "heck" -version = "0.5.0" +name = "unicode-ident" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] -name = "http" -version = "0.2.12" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "http" -version = "1.2.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "bytes", - "fnv", - "itoa", + "windows-targets", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.2.0", -] +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "http-body-util" -version = "0.1.2" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "pin-project-lite", -] +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "http-range-header" -version = "0.4.2" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "httparse" -version = "1.9.5" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "httpdate" -version = "1.0.3" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "hyper" -version = "0.14.31" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "hyper" -version = "1.5.1" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", -] +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.31", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "hyper 1.5.1", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "js-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.167" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "logos" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6b6e02facda28ca5fb8dbe4b152496ba3b1bd5a4b40bb2b1b2d8ad74e0f39b" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-codegen" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3303189202bb8a052bcd93d66b6c03e6fe70d9c7c47c0ea5e974955e54c876" -dependencies = [ - "beef", - "fnv", - "lazy_static", - "proc-macro2", - "quote", - "regex-syntax", - "rustc_version", - "syn", -] - -[[package]] -name = "logos-derive" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774a1c225576486e4fdf40b74646f672c542ca3608160d348749693ae9d456e6" -dependencies = [ - "logos-codegen", -] - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "openssl" -version = "0.10.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65ee1f9701bf938026630b455d5315f490640234259037edb259798b3bcf85e" -dependencies = [ - "num-traits", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pulldown-cmark" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" -dependencies = [ - "bitflags 2.6.0", - "memchr", - "pulldown-cmark-escape", - "unicase", -] - -[[package]] -name = "pulldown-cmark-escape" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ramhorns" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d64e28e1069d26c22d4f085ca7f9e2e2eaee16f9d6f80e5fdce62cbf8670f" -dependencies = [ - "arrayvec", - "beef", - "fnv", - "logos", - "pulldown-cmark", - "ramhorns-derive", -] - -[[package]] -name = "ramhorns-derive" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55c1eb278808d0120a6dd2109f39a8da8fd279fc9bfcd5511e9b13e41dfcae9" -dependencies = [ - "fnv", - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.31", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "rig-core" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67d6a8a31988c7e0e151bb0868e6f8bf0d4a0d01ba57c46a84ad3f354c55cc18" -dependencies = [ - "futures", - "glob", - "ordered-float", - "reqwest", - "schemars", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" -dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64", -] - -[[package]] -name = "rustversion" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 0.1.2", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" -dependencies = [ - "bitflags 2.6.0", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "http-range-header", - "httpdate", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicase" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" - -[[package]] -name = "web-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 2f4b16d4e0..cdc3789b53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7.9" -derive_more = { version = "1.0.0", features = ["debug", "from"] } -derive_setters = "0.1.6" -ramhorns = "1.0.1" -rig-core = "0.5.0" -serde = { version = "1.0.215", features = ["derive"] } -serde_json = "1.0.133" -tokio = { version = "1.42.0", features = ["rt-multi-thread", "fs"] } -tower = { version = "0.5.1", features = ["util"] } -tower-http = { version = "0.6.2", features = ["fs", "trace"] } +tokio-stream = "0.1.17" +tokio = { version = "1.42.0", features = ["full"] } diff --git a/assets/index.html b/assets/index.html deleted file mode 100644 index 48b7e91199..0000000000 --- a/assets/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - LLM API Interaction - - - -
-

Alchemy

-
-
- - -
- -
-
-
- - - - diff --git a/example/.spec/todo-app.md b/example/.spec/todo-app.md deleted file mode 100644 index 8e6c560144..0000000000 --- a/example/.spec/todo-app.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: todo-app ---- - -# Specifications - -- Create a simple TODO application and expose REST APIs to interact with it. -- I should be able to create a TODO item, update it, delete it, and list all the TODO items. -- The List API should support pagination. -- I should be able to filter the TODO items based on the `done` field. -- I should be able to undo and redo the last action. -- It should be able to mark a TODO item as done. -- API Endpoints: - - `POST /todos` - Create a TODO item - - `GET /todos` - List all TODO items (pagination supported) - - `GET /todos/{id}` - Get a TODO item by ID - - `PUT /todos/{id}` - Update a TODO item by ID - - `DELETE /todos/{id}` - Delete a TODO item by ID - - `POST /todos/{id}/done` - Mark a TODO item as done - - `POST /todos/undo` - Undo the last action - - `POST /todos/redo` - Redo the last action - - `GET /todos/history` - Get the history of actions (pagination supported) - -# Technical Requirements - -- It should be written in Rust -- Use Axum as the web framework -- The backend should be written in surrealDB -- Add unit tests for the backend diff --git a/src/api.rs b/src/api.rs deleted file mode 100644 index 26ab1aa707..0000000000 --- a/src/api.rs +++ /dev/null @@ -1,25 +0,0 @@ -use axum::{ - routing::post, - Router, -}; - -use crate::exec::Exec; - -pub struct Api { - router: Router, -} - -impl Api { - pub fn new() -> Self { - let router = Router::new().route( - "/exec", - post(|req| async { Exec::new().execute(req).await }), - ); - - Api { router } - } - - pub fn into_router(self) -> Router { - self.router - } -} diff --git a/src/cause.rs b/src/cause.rs deleted file mode 100644 index 9b3e636e46..0000000000 --- a/src/cause.rs +++ /dev/null @@ -1,32 +0,0 @@ -use axum::{ - body::Body, - response::{IntoResponse, Response}, -}; -use derive_setters::Setters; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Setters)] -#[setters(strip_option, into)] -pub struct Cause { - error: String, - cause: Option>, -} - -impl IntoResponse for Cause { - fn into_response(self) -> Response { - Response::builder() - .status(400) - .header("Content-Type", "application/json") - .body(Body::from(serde_json::to_string(&self).unwrap())) - .unwrap() - } -} - -impl Cause { - pub fn new(error: impl Into) -> Cause { - Cause { - error: error.into(), - cause: None, - } - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index d84e135109..0000000000 --- a/src/error.rs +++ /dev/null @@ -1,47 +0,0 @@ -use axum::response::{IntoResponse, Response}; -use derive_more::derive::{Debug, From}; -use rig::completion::PromptError; - -use crate::cause::Cause; - -#[derive(Debug, From)] -pub enum Error { - IO { - error: tokio::io::Error, - resource: String, - }, - Axum { - error: axum::Error, - }, - Serde { - error: serde_json::Error, - }, - Prompt { - error: PromptError, - }, -} - -pub type Result
= std::result::Result; - -impl From for Cause { - fn from(value: Error) -> Self { - match value { - Error::IO { error, resource } => { - Cause::new(format!("IO Error: {}", resource)).cause(Cause::new(error.to_string())) - } - Error::Axum { error } => Cause::new("Axum Error").cause(Cause::new(error.to_string())), - Error::Serde { error } => { - Cause::new("Serde Error").cause(Cause::new(error.to_string())) - } - Error::Prompt { error } => { - Cause::new("Prompt Error").cause(Cause::new(error.to_string())) - } - } - } -} - -impl IntoResponse for Error { - fn into_response(self) -> Response { - Cause::from(self).into_response() - } -} diff --git a/src/exec.rs b/src/exec.rs deleted file mode 100644 index fd572d51a8..0000000000 --- a/src/exec.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::error::Result; -use axum::body::{to_bytes, Body}; -use axum::http::HeaderValue; -use axum::{ - http::{Request, Response}, - response::IntoResponse, -}; -use derive_more::Debug; -use rig::agent::Agent; -use rig::completion::Prompt; -use rig::providers::openai::{self, CompletionModel}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone)] -pub struct Exec {} - -impl Exec { - pub fn new() -> Self { - Exec {} - } - - pub async fn execute(&self, request: Request) -> Result> { - let (_, body) = request.into_parts(); - let bytes = to_bytes(body, usize::MAX).await?; - let prompt = serde_json::from_slice(bytes.as_ref())?; - let action = LLMAgent::default().execute(prompt).await?; - let mut response = Response::new(Body::from(serde_json::to_vec(&action)?).into()); - response - .headers_mut() - .append("Content-Type", HeaderValue::from_static("application/json")); - Ok(response) - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -enum Action { - Prompt(String), -} - -impl Action { - async fn from_request(request: Request) -> Result { - let (_, body) = request.into_parts(); - let bytes = to_bytes(body, usize::MAX).await?; - Ok(serde_json::from_slice(bytes.as_ref())?) - } -} - -#[derive(Debug, Serialize)] -enum Command { - Submit(String), -} - -impl IntoResponse for Command { - fn into_response(self) -> axum::response::Response { - match serde_json::to_vec(&self) { - Ok(body) => axum::http::Response::new(Body::from(body)).into_response(), - Err(_) => todo!(), - } - } -} - -struct LLMAgent { - agent: Agent, -} - -impl LLMAgent { - fn default() -> Self { - // A place to initialize our LLM agent - let agent = openai::Client::from_env().agent("gpt-4o-mini").build(); - LLMAgent { agent } - } - - async fn execute(&self, action: Action) -> Result { - match action { - Action::Prompt(message) => { - let response = self.agent.prompt(message.as_str()).await?; - Ok(Command::Submit(response)) - } - } - } -} diff --git a/src/main.rs b/src/main.rs index ed6d5b4385..7e3d561dfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,4 @@ -mod api; -mod cause; -mod error; -mod exec; - -use api::Api; -use tower_http::services::ServeDir; - -use axum::{self, Router}; - #[tokio::main] async fn main() { - let api = Api::new(); - let app = Router::new() - .nest("/api", api.into_router()) - .nest_service("/assets", ServeDir::new("assets")); - - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - axum::serve(listener, app).await.unwrap(); + println!("Hello, world!"); } diff --git a/src/prompts.rs b/src/prompts.rs deleted file mode 100644 index f18b68dd4c..0000000000 --- a/src/prompts.rs +++ /dev/null @@ -1 +0,0 @@ -pub const INITIALIZE: &str = include_str!("./prompts/initialize.md"); diff --git a/src/prompts/initialize.md b/src/prompts/initialize.md deleted file mode 100644 index 64686a2e20..0000000000 --- a/src/prompts/initialize.md +++ /dev/null @@ -1,3 +0,0 @@ -Understand the product specification below and create an high-level plan to implement the product. - -{{specification}} From 31c9c94b7bff6986dc11b2dc7ec2be9171fc263f Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Fri, 13 Dec 2024 22:35:39 -0800 Subject: [PATCH 04/15] add compiling code --- Cargo.lock | 461 ++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 8 +- src/main.rs | 235 +++++++++++++++++++++++++- 3 files changed, 685 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e029504ecc..6bc4c2a833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "autocfg" version = "1.4.0" @@ -35,7 +41,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -50,6 +56,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cfg-if" version = "1.0.0" @@ -60,22 +72,184 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "code-forge" version = "0.1.0" dependencies = [ + "crossterm", + "futures", + "ratatui", + "thiserror", "tokio", "tokio-stream", ] +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "libc" version = "0.2.168" @@ -92,6 +266,21 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -107,6 +296,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -115,7 +316,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -147,15 +348,27 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.92" @@ -174,6 +387,25 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ratatui" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" +dependencies = [ + "bitflags", + "cassowary", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -189,12 +421,39 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -204,6 +463,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -217,7 +485,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "stability" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.90", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -231,6 +542,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "tokio" version = "1.42.0" @@ -240,13 +571,13 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -257,7 +588,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -277,19 +608,77 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -298,28 +687,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -332,24 +739,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index cdc3789b53..1dece9216b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -tokio-stream = "0.1.17" -tokio = { version = "1.42.0", features = ["full"] } +tokio = { version = "1", features = ["full"] } +ratatui = { version = "0.25", features = ["crossterm"] } +crossterm = "0.27" +futures = "0.3" +tokio-stream = "0.1" +thiserror = "1.0" diff --git a/src/main.rs b/src/main.rs index 7e3d561dfe..113d90a4b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,235 @@ +use std::error::Error; +use std::fs; +use std::io::{self, Write}; +use std::sync::Arc; +use std::time::Duration; + +use crossterm::{ + event::{self, Event, KeyCode, KeyEventKind}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use futures::stream::Stream; +use futures::StreamExt; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, Paragraph, Wrap}, +}; +use tokio::sync::mpsc; +use tokio_stream; + +#[derive(Debug, Clone)] +struct Agent {} + +impl Agent { + fn new() -> Agent { + Agent {} + } + + /// Method to write a file (added back to maintain original functionality) + fn write_file(&self, path: &str, content: &str) -> Result<(), std::io::Error> { + let mut file = fs::File::create(path)?; + file.write_all(content.as_bytes())?; + Ok(()) + } + + /// Simulate a streaming response from an AI agent + async fn stream_response(&self) -> impl Stream { + let (tx, rx) = mpsc::channel(5); + + tokio::spawn(async move { + let response_parts = vec![ + "Hello, ", + "this is ", + "a simulated ", + "streaming response ", + "from the AI agent.", + ]; + + for part in response_parts { + tokio::time::sleep(Duration::from_millis(200)).await; + tx.send(part.to_string()).await.unwrap(); + } + }); + + tokio_stream::wrappers::ReceiverStream::new(rx) + } +} + +/// UI state for the chat interface +#[derive(Clone)] +struct ChatState { + input: String, + messages: Vec, + streaming_response: Option, +} + +impl ChatState { + fn new() -> Self { + Self { + input: String::new(), + messages: Vec::new(), + streaming_response: None, + } + } +} + +/// Render the chat UI +fn render_chat_ui(frame: &mut Frame, state: &ChatState) -> Result<(), Box> { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Min(10), // Messages area + Constraint::Length(3), // Input area + ]) + .split(frame.size()); + + // Messages area + let messages_block = Paragraph::new( + state + .messages + .iter() + .chain(state.streaming_response.iter()) + .cloned() + .collect::>() + .join("\n"), + ) + .block( + Block::default() + .borders(Borders::ALL) + .title("Chat Messages"), + ) + .wrap(Wrap { trim: true }); + frame.render_widget(messages_block, layout[0]); + + // Input area + let input_block = Paragraph::new(state.input.clone()) + .block(Block::default().borders(Borders::ALL).title("Input")); + frame.render_widget(input_block, layout[1]); + + Ok(()) +} + +/// Main chat loop handling UI and interactions +async fn chat_loop() -> Result<(), Box> { + // Terminal setup + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + // Chat state and channels for async communication + let chat_state_main = Arc::new(tokio::sync::Mutex::new(ChatState::new())); + let (input_tx, mut input_rx) = mpsc::channel(100); + let (state_tx, mut state_rx) = mpsc::channel(100); + + // Spawn a task to handle input events + + let chat_state = chat_state_main.clone(); + tokio::spawn(async move { + loop { + if let Ok(Event::Key(key)) = event::read() { + if key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Enter => { + // Safely get and clear input + let input = { + let mut state = chat_state.lock().await; + let input = state.input.clone(); + state.input.clear(); + input + }; + + // Send input and signal state update + if !input.is_empty() { + input_tx.send(input).await.unwrap(); + state_tx.send(()).await.unwrap(); + } + } + KeyCode::Char(c) => { + let mut state = chat_state.lock().await; + state.input.push(c); + state_tx.send(()).await.unwrap(); + } + KeyCode::Backspace => { + let mut state = chat_state.lock().await; + state.input.pop(); + state_tx.send(()).await.unwrap(); + } + KeyCode::Esc => { + break; + } + _ => {} + } + } + } + } + }); + + // Main rendering and message processing loop + let leader = Agent::new(); + + // Example of file manipulation (from original code) + match leader.write_file("example.txt", "Hello, file manipulation!") { + Ok(_) => println!("File created successfully"), + Err(e) => eprintln!("Error creating file: {}", e), + } + + while let Some(message) = input_rx.recv().await { + // Add user message to chat + { + let mut state = chat_state_main.lock().await; + state.messages.push(format!("User: {}", message)); + } + + // Clear any previous streaming response + { + let mut state = chat_state_main.lock().await; + state.streaming_response = None; + } + + // Simulate streaming AI response + let mut response_stream = leader.stream_response().await; + + // Render loop for streaming response + while let Some(part) = response_stream.next().await { + { + let mut state = chat_state_main.lock().await; + state.streaming_response = Some(part); + } + + // Render terminal + let content = &chat_state_main.lock().await; + terminal.draw(|frame| { + render_chat_ui(frame, &content).unwrap(); + })?; + } + + // Add full response to messages + { + let mut state = chat_state_main.lock().await; + if let Some(full_response) = state.streaming_response.take() { + state.messages.push(format!("AI: {}", full_response)); + } + } + + // Consume any state update signals + while state_rx.try_recv().is_ok() {} + } + + // Cleanup + disable_raw_mode()?; + execute!(terminal.backend_mut(), LeaveAlternateScreen)?; + terminal.show_cursor()?; + + Ok(()) +} + #[tokio::main] -async fn main() { - println!("Hello, world!"); +async fn main() -> Result<(), Box> { + // Start the chat interface + chat_loop().await?; + + Ok(()) } From 36902e938bcf943b2fb5d32a5545a74a7bd89e58 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Fri, 13 Dec 2024 23:26:10 -0800 Subject: [PATCH 05/15] add basic LLM integration --- Cargo.lock | 1267 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + src/chat_engine.rs | 113 ++++ src/chat_ui.rs | 256 +++++++++ src/main.rs | 257 ++------- 5 files changed, 1650 insertions(+), 246 deletions(-) create mode 100644 src/chat_engine.rs create mode 100644 src/chat_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 6bc4c2a833..d4d1d74fb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,12 +23,69 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "async-convert" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d416feee97712e43152cd42874de162b8f9b77295b1c85e5d92725cc8310bae" +dependencies = [ + "async-trait", +] + +[[package]] +name = "async-openai" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e1df052c2bd7b241fc828bc2fda74ce9a7ef05e0a593c37275aaaba52caf49d" +dependencies = [ + "async-convert", + "backoff", + "base64", + "derive_builder", + "futures", + "rand", + "reqwest", + "reqwest-eventsource", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom", + "instant", + "pin-project-lite", + "rand", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -44,12 +101,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.9.0" @@ -62,6 +143,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "cc" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -72,21 +162,40 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "code-forge" version = "0.1.0" dependencies = [ + "async-openai", "crossterm", "futures", "ratatui", + "serde", + "serde_json", "thiserror", "tokio", "tokio-stream", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossterm" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.6.0", "crossterm_winapi", "libc", "mio 0.8.11", @@ -105,24 +214,136 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -194,6 +415,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -212,12 +439,42 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -235,12 +492,254 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "itertools" version = "0.12.1" @@ -250,12 +749,34 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -287,6 +808,28 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -319,6 +862,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "object" version = "0.36.5" @@ -328,6 +881,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "parking_lot" version = "0.12.3" @@ -357,6 +922,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -369,6 +940,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -388,50 +968,296 @@ dependencies = [ ] [[package]] -name = "ratatui" -version = "0.25.0" +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ratatui" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f03f570355882dd8d15acc3a313841e6e90eddbc76a93c748fd82cc13ba9f51" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest", + "thiserror", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ - "bitflags", - "cassowary", - "crossterm", - "indoc", - "itertools", - "lru", - "paste", - "stability", - "strum", - "unicode-segmentation", - "unicode-width", + "serde_derive", ] [[package]] -name = "redox_syscall" -version = "0.5.7" +name = "serde_derive" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ - "bitflags", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "serde_json" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] [[package]] -name = "rustversion" -version = "1.0.18" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -488,6 +1314,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stability" version = "0.1.1" @@ -498,6 +1330,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.25.0" @@ -542,6 +1386,44 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -562,6 +1444,16 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.42.0" @@ -591,6 +1483,16 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -602,6 +1504,68 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -620,12 +1584,140 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.90", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -666,6 +1758,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -786,3 +1887,113 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] diff --git a/Cargo.toml b/Cargo.toml index 1dece9216b..974ebe3e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,6 @@ crossterm = "0.27" futures = "0.3" tokio-stream = "0.1" thiserror = "1.0" +async-openai = "0.14" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/chat_engine.rs b/src/chat_engine.rs new file mode 100644 index 0000000000..1f07f51369 --- /dev/null +++ b/src/chat_engine.rs @@ -0,0 +1,113 @@ +use std::env; +use async_openai::{ + types::{ + ChatCompletionRequestMessage, + CreateChatCompletionRequest, + Role, + ChatCompletionRequestMessageArgs, + }, + Client, +}; +use futures::stream::Stream; +use futures::StreamExt; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +pub struct Agent { + system_prompt: String, + user_prompt: String, + client: Client, + messages: Vec, +} + +impl Agent { + pub fn new(system: String, user: String) -> Self { + let client = Client::new(); + + let mut messages = Vec::new(); + messages.push( + ChatCompletionRequestMessageArgs::default() + .role(Role::System) + .content(system.clone()) + .build() + .unwrap() + ); + + Self { + system_prompt: system, + user_prompt: user, + client, + messages, + } + } + + /// Get a streaming response from OpenAI + pub async fn stream_response(&mut self, input: String) -> impl Stream + Unpin { + let (tx, rx) = mpsc::channel::(100); + + // Add user message to history + self.messages.push( + ChatCompletionRequestMessageArgs::default() + .role(Role::User) + .content(input) + .build() + .unwrap() + ); + + // Create chat completion request + let request = CreateChatCompletionRequest { + model: "gpt-3.5-turbo".to_string(), + messages: self.messages.clone(), + temperature: Some(0.7), + stream: Some(true), + max_tokens: Some(1000), + ..Default::default() + }; + + let client = self.client.clone(); + + // Spawn task to handle streaming response + tokio::spawn(async move { + let mut stream = client.chat().create_stream(request).await.unwrap(); + let mut current_content = String::new(); + + while let Some(result) = stream.next().await { + match result { + Ok(response) => { + if let Some(delta) = &response.choices[0].delta.content { + current_content.push_str(delta); + let _ = tx.send(delta.clone()).await; + } + } + Err(e) => { + let _ = tx.send(format!("Error: {}", e)).await; + break; + } + } + } + + // Store assistant's message in history + if !current_content.is_empty() { + let _ = tx.send("\n".to_string()).await; + } + }); + + ReceiverStream::new(rx) + } +} + +pub struct ChatEngine { + agent: Agent, +} + +impl ChatEngine { + pub fn new(system_prompt: String, user_prompt: String) -> Self { + Self { + agent: Agent::new(system_prompt, user_prompt), + } + } + + pub async fn process_message(&mut self, input: String) -> impl Stream + Unpin { + self.agent.stream_response(input).await + } +} diff --git a/src/chat_ui.rs b/src/chat_ui.rs new file mode 100644 index 0000000000..f6f7918f5c --- /dev/null +++ b/src/chat_ui.rs @@ -0,0 +1,256 @@ +use std::error::Error; +use std::io::{self}; + +use crossterm::{ + event::{self, Event, KeyCode, KeyEventKind}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use futures::stream::Stream; +use futures::StreamExt; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, Paragraph, Wrap}, + style::{Style, Modifier}, +}; +use tokio::sync::{mpsc, Mutex}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct ChatState { + input: String, + cursor_position: usize, + messages: Vec, + streaming_response: Option, +} + +impl ChatState { + pub fn new() -> Self { + Self { + input: String::new(), + cursor_position: 0, + messages: Vec::new(), + streaming_response: None, + } + } +} + +pub struct ChatUI { + terminal: Terminal>, + state: Arc>, +} + +impl ChatUI { + pub fn new() -> Result> { + // Terminal setup + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let terminal = Terminal::new(backend)?; + + Ok(Self { + terminal, + state: Arc::new(Mutex::new(ChatState::new())), + }) + } + + fn render_chat_ui(frame: &mut Frame, state: &ChatState) -> Result<(), Box> { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Min(10), // Messages area + Constraint::Length(3), // Input area + ]) + .split(frame.size()); + + // Messages area + let messages_block = Paragraph::new( + state.messages.iter() + .chain(state.streaming_response.iter()) + .cloned() + .collect::>() + .join("\n") + ) + .block(Block::default().borders(Borders::ALL).title("Chat Messages")) + .wrap(Wrap { trim: true }); + frame.render_widget(messages_block, layout[0]); + + // Input area with cursor + let input_len = state.input.len(); + let cursor_style = Style::default().add_modifier(Modifier::REVERSED); + + let input_text = if state.cursor_position < input_len { + let (before, at_cursor) = state.input.split_at(state.cursor_position); + let (at_cursor, after) = at_cursor.split_at(1); + Line::from(vec![ + Span::raw(before), + Span::styled(at_cursor, cursor_style), + Span::raw(after), + ]) + } else if input_len > 0 { + Line::from(vec![ + Span::raw(&state.input), + Span::styled(" ", cursor_style), + ]) + } else { + Line::from(vec![Span::styled(" ", cursor_style)]) + }; + + let input_block = Paragraph::new(input_text) + .block(Block::default().borders(Borders::ALL).title("Input")); + frame.render_widget(input_block, layout[1]); + + Ok(()) + } + + pub async fn run( + &mut self, + mut response_stream: S, + ) -> Result<(), Box> + where + S: Stream + Unpin, + { + let (state_tx, mut state_rx) = mpsc::channel(100); + let (input_tx, mut input_rx) = mpsc::channel(100); + let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1); + + // Set up Ctrl+C handler + let shutdown_tx_clone = shutdown_tx.clone(); + tokio::spawn(async move { + if let Ok(()) = tokio::signal::ctrl_c().await { + let _ = shutdown_tx_clone.send(()).await; + } + }); + + // Spawn input handling task + let state_clone = Arc::clone(&self.state); + let shutdown_tx_clone = shutdown_tx.clone(); + let input_handle = tokio::spawn(async move { + loop { + if let Ok(Event::Key(key)) = event::read() { + if key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Enter => { + let mut state = state_clone.lock().await; + let input = state.input.clone(); + if !input.is_empty() { + state.messages.push(format!("User: {}", input)); + let _ = input_tx.send(input).await; + } + state.input.clear(); + state.cursor_position = 0; + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Char(c) => { + let mut state = state_clone.lock().await; + let pos = state.cursor_position; + if pos == state.input.len() { + state.input.push(c); + } else { + state.input.insert(pos, c); + } + state.cursor_position += 1; + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Backspace => { + let mut state = state_clone.lock().await; + if state.cursor_position > 0 { + let pos = state.cursor_position - 1; + state.input.remove(pos); + state.cursor_position = pos; + } + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Delete => { + let mut state = state_clone.lock().await; + let pos = state.cursor_position; + if pos < state.input.len() { + state.input.remove(pos); + } + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Left => { + let mut state = state_clone.lock().await; + if state.cursor_position > 0 { + state.cursor_position -= 1; + } + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Right => { + let mut state = state_clone.lock().await; + if state.cursor_position < state.input.len() { + state.cursor_position += 1; + } + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Home => { + let mut state = state_clone.lock().await; + state.cursor_position = 0; + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::End => { + let mut state = state_clone.lock().await; + state.cursor_position = state.input.len(); + drop(state); + let _ = state_tx.send(()).await; + }, + KeyCode::Esc => { + let _ = shutdown_tx_clone.send(()).await; + break; + } + _ => {} + } + } + } + } + }); + + // Process responses + loop { + tokio::select! { + Some(response) = response_stream.next() => { + let mut state = self.state.lock().await; + state.messages.push(format!("AI: {}", response)); + let state_snapshot = state.clone(); + drop(state); + + self.terminal.draw(|frame| { + Self::render_chat_ui(frame, &state_snapshot).unwrap() + })?; + } + Some(_) = state_rx.recv() => { + let state_snapshot = self.state.lock().await.clone(); + self.terminal.draw(|frame| { + Self::render_chat_ui(frame, &state_snapshot).unwrap() + })?; + } + Some(_) = shutdown_rx.recv() => { + break; + } + else => break, + } + } + + // Clean up input handling task + input_handle.abort(); + + Ok(()) + } +} + +impl Drop for ChatUI { + fn drop(&mut self) { + // Cleanup terminal + disable_raw_mode().unwrap(); + execute!(self.terminal.backend_mut(), LeaveAlternateScreen).unwrap(); + self.terminal.show_cursor().unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs index 113d90a4b4..fbb2036c16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,235 +1,56 @@ -use std::error::Error; -use std::fs; -use std::io::{self, Write}; -use std::sync::Arc; -use std::time::Duration; +mod chat_engine; +mod chat_ui; -use crossterm::{ - event::{self, Event, KeyCode, KeyEventKind}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; +use std::error::Error; use futures::stream::Stream; use futures::StreamExt; -use ratatui::{ - prelude::*, - widgets::{Block, Borders, Paragraph, Wrap}, -}; use tokio::sync::mpsc; -use tokio_stream; - -#[derive(Debug, Clone)] -struct Agent {} - -impl Agent { - fn new() -> Agent { - Agent {} - } - - /// Method to write a file (added back to maintain original functionality) - fn write_file(&self, path: &str, content: &str) -> Result<(), std::io::Error> { - let mut file = fs::File::create(path)?; - file.write_all(content.as_bytes())?; - Ok(()) - } - - /// Simulate a streaming response from an AI agent - async fn stream_response(&self) -> impl Stream { - let (tx, rx) = mpsc::channel(5); - - tokio::spawn(async move { - let response_parts = vec![ - "Hello, ", - "this is ", - "a simulated ", - "streaming response ", - "from the AI agent.", - ]; - - for part in response_parts { - tokio::time::sleep(Duration::from_millis(200)).await; - tx.send(part.to_string()).await.unwrap(); - } - }); - - tokio_stream::wrappers::ReceiverStream::new(rx) - } -} - -/// UI state for the chat interface -#[derive(Clone)] -struct ChatState { - input: String, - messages: Vec, - streaming_response: Option, -} - -impl ChatState { - fn new() -> Self { - Self { - input: String::new(), - messages: Vec::new(), - streaming_response: None, - } - } -} - -/// Render the chat UI -fn render_chat_ui(frame: &mut Frame, state: &ChatState) -> Result<(), Box> { - let layout = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Min(10), // Messages area - Constraint::Length(3), // Input area - ]) - .split(frame.size()); - - // Messages area - let messages_block = Paragraph::new( - state - .messages - .iter() - .chain(state.streaming_response.iter()) - .cloned() - .collect::>() - .join("\n"), - ) - .block( - Block::default() - .borders(Borders::ALL) - .title("Chat Messages"), - ) - .wrap(Wrap { trim: true }); - frame.render_widget(messages_block, layout[0]); - - // Input area - let input_block = Paragraph::new(state.input.clone()) - .block(Block::default().borders(Borders::ALL).title("Input")); - frame.render_widget(input_block, layout[1]); - - Ok(()) -} - -/// Main chat loop handling UI and interactions -async fn chat_loop() -> Result<(), Box> { - // Terminal setup - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen)?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - // Chat state and channels for async communication - let chat_state_main = Arc::new(tokio::sync::Mutex::new(ChatState::new())); - let (input_tx, mut input_rx) = mpsc::channel(100); - let (state_tx, mut state_rx) = mpsc::channel(100); - - // Spawn a task to handle input events - - let chat_state = chat_state_main.clone(); +use tokio_stream::wrappers::ReceiverStream; +use chat_engine::ChatEngine; +use chat_ui::ChatUI; + +async fn create_response_stream( + mut chat_engine: ChatEngine, + mut input_rx: mpsc::Receiver, +) -> impl Stream + Unpin { + let (tx, rx) = mpsc::channel(100); + + // Spawn a task to process inputs and generate responses tokio::spawn(async move { - loop { - if let Ok(Event::Key(key)) = event::read() { - if key.kind == KeyEventKind::Press { - match key.code { - KeyCode::Enter => { - // Safely get and clear input - let input = { - let mut state = chat_state.lock().await; - let input = state.input.clone(); - state.input.clear(); - input - }; - - // Send input and signal state update - if !input.is_empty() { - input_tx.send(input).await.unwrap(); - state_tx.send(()).await.unwrap(); - } - } - KeyCode::Char(c) => { - let mut state = chat_state.lock().await; - state.input.push(c); - state_tx.send(()).await.unwrap(); - } - KeyCode::Backspace => { - let mut state = chat_state.lock().await; - state.input.pop(); - state_tx.send(()).await.unwrap(); - } - KeyCode::Esc => { - break; - } - _ => {} - } - } + // Send welcome message + let _ = tx.send("Welcome! Type a message to begin.".to_string()).await; + + // Process incoming messages + while let Some(input) = input_rx.recv().await { + let mut response_stream = chat_engine.process_message(input).await; + + while let Some(response_part) = response_stream.next().await { + let _ = tx.send(response_part).await; } } }); - // Main rendering and message processing loop - let leader = Agent::new(); - - // Example of file manipulation (from original code) - match leader.write_file("example.txt", "Hello, file manipulation!") { - Ok(_) => println!("File created successfully"), - Err(e) => eprintln!("Error creating file: {}", e), - } - - while let Some(message) = input_rx.recv().await { - // Add user message to chat - { - let mut state = chat_state_main.lock().await; - state.messages.push(format!("User: {}", message)); - } - - // Clear any previous streaming response - { - let mut state = chat_state_main.lock().await; - state.streaming_response = None; - } - - // Simulate streaming AI response - let mut response_stream = leader.stream_response().await; - - // Render loop for streaming response - while let Some(part) = response_stream.next().await { - { - let mut state = chat_state_main.lock().await; - state.streaming_response = Some(part); - } - - // Render terminal - let content = &chat_state_main.lock().await; - terminal.draw(|frame| { - render_chat_ui(frame, &content).unwrap(); - })?; - } - - // Add full response to messages - { - let mut state = chat_state_main.lock().await; - if let Some(full_response) = state.streaming_response.take() { - state.messages.push(format!("AI: {}", full_response)); - } - } - - // Consume any state update signals - while state_rx.try_recv().is_ok() {} - } - - // Cleanup - disable_raw_mode()?; - execute!(terminal.backend_mut(), LeaveAlternateScreen)?; - terminal.show_cursor()?; - - Ok(()) + ReceiverStream::new(rx) } #[tokio::main] async fn main() -> Result<(), Box> { + // Initialize chat components + let chat_engine = ChatEngine::new( + "You are a helpful AI assistant. Provide clear, concise responses.".to_string(), + "Start a conversation".to_string() + ); + + let mut chat_ui = ChatUI::new()?; + + // Create channels for user input + let (input_tx, input_rx) = mpsc::channel(100); + + // Create response stream + let response_stream = create_response_stream(chat_engine, input_rx).await; + // Start the chat interface - chat_loop().await?; + chat_ui.run(response_stream).await?; Ok(()) } From 5f10171a53051c57b435d64b71def6d206c55385 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sat, 14 Dec 2024 00:10:04 -0800 Subject: [PATCH 06/15] update code --- Cargo.lock | 157 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/chat_engine.rs | 92 +++++++++++++++++++++++--- src/chat_ui.rs | 69 ++++++++++++-------- src/main.rs | 51 +++++++++++---- 5 files changed, 320 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4d1d74fb6..c856c5a4c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,7 @@ dependencies = [ "crossterm", "futures", "ratatui", + "reqwest", "serde", "serde_json", "thiserror", @@ -312,6 +313,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "eventsource-stream" version = "0.2.3" @@ -323,6 +334,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fnv" version = "1.0.7" @@ -335,6 +352,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -564,6 +596,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -771,6 +816,12 @@ version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.4" @@ -862,6 +913,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -887,12 +955,50 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -940,6 +1046,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1041,11 +1153,13 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1058,6 +1172,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", @@ -1106,6 +1221,19 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -1424,6 +1552,19 @@ dependencies = [ "libc", ] +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1483,6 +1624,16 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1613,6 +1764,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 974ebe3e60..b058fc25a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ futures = "0.3" tokio-stream = "0.1" thiserror = "1.0" async-openai = "0.14" +reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/src/chat_engine.rs b/src/chat_engine.rs index 1f07f51369..f9e74761d0 100644 --- a/src/chat_engine.rs +++ b/src/chat_engine.rs @@ -6,23 +6,69 @@ use async_openai::{ Role, ChatCompletionRequestMessageArgs, }, - Client, + Client, config::Config, }; use futures::stream::Stream; use futures::StreamExt; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}; + +const OPENROUTER_BASE_URL: &str = "https://openrouter.ai/api/v1"; +const MODEL_NAME: &str = "anthropic/claude-3-sonnet"; + +#[derive(Clone)] +struct OpenRouterConfig { + api_key: String, +} + +impl Config for OpenRouterConfig { + fn api_key(&self) -> &str { + &self.api_key + } + + fn api_base(&self) -> &str { + OPENROUTER_BASE_URL + } + + fn headers(&self) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", self.api_key)).unwrap(), + ); + headers.insert( + CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ); + headers.insert( + "X-Title", + HeaderValue::from_static("Code Forge Chat"), + ); + headers + } + + fn url(&self, path: &str) -> String { + format!("{}/{}", self.api_base(), path) + } + + fn query(&self) -> Vec<(&str, &str)> { + Vec::new() + } +} pub struct Agent { system_prompt: String, user_prompt: String, - client: Client, + client: Client, messages: Vec, } impl Agent { pub fn new(system: String, user: String) -> Self { - let client = Client::new(); + let api_key = env::var("OPENROUTER_API_KEY").expect("OPENROUTER_API_KEY must be set"); + let config = OpenRouterConfig { api_key }; + let client = Client::with_config(config); let mut messages = Vec::new(); messages.push( @@ -41,7 +87,33 @@ impl Agent { } } - /// Get a streaming response from OpenAI + /// Test the connection to OpenRouter + pub async fn test_connection(&self) -> Result> { + let request = CreateChatCompletionRequest { + model: MODEL_NAME.to_string(), + messages: vec![ + ChatCompletionRequestMessageArgs::default() + .role(Role::System) + .content("You are a helpful AI assistant.") + .build() + .unwrap(), + ChatCompletionRequestMessageArgs::default() + .role(Role::User) + .content("Respond with 'Connected successfully!' if you receive this message.") + .build() + .unwrap(), + ], + temperature: Some(0.7), + stream: Some(false), + max_tokens: Some(50), + ..Default::default() + }; + + let response = self.client.chat().create(request).await?; + Ok(response.choices[0].message.content.clone().unwrap_or_default()) + } + + /// Get a streaming response from OpenRouter pub async fn stream_response(&mut self, input: String) -> impl Stream + Unpin { let (tx, rx) = mpsc::channel::(100); @@ -56,7 +128,7 @@ impl Agent { // Create chat completion request let request = CreateChatCompletionRequest { - model: "gpt-3.5-turbo".to_string(), + model: MODEL_NAME.to_string(), messages: self.messages.clone(), temperature: Some(0.7), stream: Some(true), @@ -74,9 +146,9 @@ impl Agent { while let Some(result) = stream.next().await { match result { Ok(response) => { - if let Some(delta) = &response.choices[0].delta.content { + if let Some(ref delta) = response.choices[0].delta.content { current_content.push_str(delta); - let _ = tx.send(delta.clone()).await; + let _ = tx.send(delta.to_string()).await; } } Err(e) => { @@ -86,7 +158,7 @@ impl Agent { } } - // Store assistant's message in history + // Add assistant's message to history if !current_content.is_empty() { let _ = tx.send("\n".to_string()).await; } @@ -107,6 +179,10 @@ impl ChatEngine { } } + pub async fn test_connection(&self) -> Result> { + self.agent.test_connection().await + } + pub async fn process_message(&mut self, input: String) -> impl Stream + Unpin { self.agent.stream_response(input).await } diff --git a/src/chat_ui.rs b/src/chat_ui.rs index f6f7918f5c..ceb2d513b6 100644 --- a/src/chat_ui.rs +++ b/src/chat_ui.rs @@ -33,6 +33,30 @@ impl ChatState { streaming_response: None, } } + + fn insert_char(&mut self, c: char) { + let pos = self.cursor_position; + if pos == self.input.len() { + self.input.push(c); + } else { + self.input.insert(pos, c); + } + self.cursor_position += 1; + } + + fn backspace(&mut self) { + if self.cursor_position > 0 { + let pos = self.cursor_position - 1; + self.input.remove(pos); + self.cursor_position = pos; + } + } + + fn delete(&mut self) { + if self.cursor_position < self.input.len() { + self.input.remove(self.cursor_position); + } + } } pub struct ChatUI { @@ -64,16 +88,18 @@ impl ChatUI { ]) .split(frame.size()); - // Messages area - let messages_block = Paragraph::new( - state.messages.iter() - .chain(state.streaming_response.iter()) - .cloned() - .collect::>() - .join("\n") - ) - .block(Block::default().borders(Borders::ALL).title("Chat Messages")) - .wrap(Wrap { trim: true }); + // Messages area with scrolling + let messages_text = state.messages.iter() + .chain(state.streaming_response.iter()) + .cloned() + .collect::>() + .join("\n"); + + let messages_block = Paragraph::new(messages_text) + .block(Block::default().borders(Borders::ALL).title("Chat Messages")) + .wrap(Wrap { trim: true }) + .scroll((state.messages.len() as u16, 0)); + frame.render_widget(messages_block, layout[0]); // Input area with cursor @@ -135,7 +161,7 @@ impl ChatUI { let mut state = state_clone.lock().await; let input = state.input.clone(); if !input.is_empty() { - state.messages.push(format!("User: {}", input)); + state.messages.push(format!("You: {}", input)); let _ = input_tx.send(input).await; } state.input.clear(); @@ -145,32 +171,19 @@ impl ChatUI { }, KeyCode::Char(c) => { let mut state = state_clone.lock().await; - let pos = state.cursor_position; - if pos == state.input.len() { - state.input.push(c); - } else { - state.input.insert(pos, c); - } - state.cursor_position += 1; + state.insert_char(c); drop(state); let _ = state_tx.send(()).await; }, KeyCode::Backspace => { let mut state = state_clone.lock().await; - if state.cursor_position > 0 { - let pos = state.cursor_position - 1; - state.input.remove(pos); - state.cursor_position = pos; - } + state.backspace(); drop(state); let _ = state_tx.send(()).await; }, KeyCode::Delete => { let mut state = state_clone.lock().await; - let pos = state.cursor_position; - if pos < state.input.len() { - state.input.remove(pos); - } + state.delete(); drop(state); let _ = state_tx.send(()).await; }, @@ -218,7 +231,7 @@ impl ChatUI { tokio::select! { Some(response) = response_stream.next() => { let mut state = self.state.lock().await; - state.messages.push(format!("AI: {}", response)); + state.messages.push(format!("Assistant: {}", response)); let state_snapshot = state.clone(); drop(state); diff --git a/src/main.rs b/src/main.rs index fbb2036c16..ab63a06ea4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,9 +17,6 @@ async fn create_response_stream( // Spawn a task to process inputs and generate responses tokio::spawn(async move { - // Send welcome message - let _ = tx.send("Welcome! Type a message to begin.".to_string()).await; - // Process incoming messages while let Some(input) = input_rx.recv().await { let mut response_stream = chat_engine.process_message(input).await; @@ -35,22 +32,48 @@ async fn create_response_stream( #[tokio::main] async fn main() -> Result<(), Box> { - // Initialize chat components + // Initialize chat engine let chat_engine = ChatEngine::new( - "You are a helpful AI assistant. Provide clear, concise responses.".to_string(), + "You are Claude 3 Sonnet, an AI assistant with expertise in programming, software development, and technology. \ + You excel at providing clear, accurate, and well-structured responses. When discussing code, you use proper \ + formatting and explain key concepts thoroughly. You are direct and professional, focusing on delivering \ + high-quality technical assistance while maintaining a helpful demeanor.".to_string(), "Start a conversation".to_string() ); - - let mut chat_ui = ChatUI::new()?; - // Create channels for user input - let (input_tx, input_rx) = mpsc::channel(100); + // Test connection before creating chat window + match chat_engine.test_connection().await { + Ok(_) => { + println!("🟢 Successfully connected to Claude 3 Sonnet! Starting chat interface..."); + + // Create chat UI and channels + let mut chat_ui = ChatUI::new()?; + let (input_tx, input_rx) = mpsc::channel(100); + + // Create response stream + let mut response_stream = create_response_stream(chat_engine, input_rx).await; - // Create response stream - let response_stream = create_response_stream(chat_engine, input_rx).await; + // Create welcome message + let (welcome_tx, welcome_rx) = mpsc::channel(1); + welcome_tx.send( + "Welcome to Code Forge Chat, powered by Claude 3 Sonnet.\n\ + I'm your AI programming assistant, specializing in software development and technical topics.\n\ + Feel free to ask questions about programming, architecture, best practices, or any tech-related topics.\n\ + Type your message and press Enter to send. Press Ctrl+C or Esc to exit.\n".to_string() + ).await?; + let welcome_stream = ReceiverStream::new(welcome_rx); - // Start the chat interface - chat_ui.run(response_stream).await?; + // Combine welcome message with response stream + let combined_stream = welcome_stream.chain(response_stream); - Ok(()) + // Start the chat interface + chat_ui.run(combined_stream).await?; + Ok(()) + } + Err(e) => { + eprintln!("🔴 Failed to connect to Claude 3 Sonnet: {}", e); + eprintln!("Please check your OPENROUTER_API_KEY environment variable and internet connection."); + Err(e.into()) + } + } } From fa6802015b80af9bdd5e690baedc80c7ce817dc5 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sat, 14 Dec 2024 00:10:27 -0800 Subject: [PATCH 07/15] lint fixes --- src/chat_ui.rs | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chat_ui.rs b/src/chat_ui.rs index ceb2d513b6..ffd571f704 100644 --- a/src/chat_ui.rs +++ b/src/chat_ui.rs @@ -138,7 +138,7 @@ impl ChatUI { S: Stream + Unpin, { let (state_tx, mut state_rx) = mpsc::channel(100); - let (input_tx, mut input_rx) = mpsc::channel(100); + let (input_tx, input_rx) = mpsc::channel(100); let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1); // Set up Ctrl+C handler diff --git a/src/main.rs b/src/main.rs index ab63a06ea4..b7e3d729a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,7 @@ async fn main() -> Result<(), Box> { let (input_tx, input_rx) = mpsc::channel(100); // Create response stream - let mut response_stream = create_response_stream(chat_engine, input_rx).await; + let response_stream = create_response_stream(chat_engine, input_rx).await; // Create welcome message let (welcome_tx, welcome_rx) = mpsc::channel(1); From 00242124e4f46c9bc4ebc7546a0ae92837612874 Mon Sep 17 00:00:00 2001 From: amit Date: Sat, 14 Dec 2024 10:49:46 -0800 Subject: [PATCH 08/15] fix startup --- .gitignore | 2 +- src/chat_engine.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d01bd1a990..506a7a2729 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk - +.idea # MSVC Windows builds of rustc generate these, which store debugging information *.pdb diff --git a/src/chat_engine.rs b/src/chat_engine.rs index f9e74761d0..8b973feb78 100644 --- a/src/chat_engine.rs +++ b/src/chat_engine.rs @@ -49,7 +49,7 @@ impl Config for OpenRouterConfig { } fn url(&self, path: &str) -> String { - format!("{}/{}", self.api_base(), path) + format!("{}{}", self.api_base(), path) } fn query(&self) -> Vec<(&str, &str)> { From 93b5844585dff647e319a951ee3b58f87eb30cd6 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sat, 14 Dec 2024 10:50:21 -0800 Subject: [PATCH 09/15] chore: add integration tests --- tests/integration_test.rs | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/integration_test.rs diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 0000000000..f450abfc31 --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,58 @@ +//! Integration tests for Code Forge Chat +//! +//! These tests verify the connection to OpenRouter and basic functionality +//! of the chat engine. To run these tests, make sure you have set the +//! OPENROUTER_API_KEY environment variable. + +use code_forge::ChatEngine; +use std::env; + +/// Test the connection to OpenRouter and verify we can access Claude 3 Sonnet +#[tokio::test] +async fn test_openrouter_connection() { + // Verify API key is set + if env::var("OPENROUTER_API_KEY").is_err() { + panic!("❌ OPENROUTER_API_KEY environment variable is not set"); + } + + let chat_engine = ChatEngine::new( + "You are a test assistant.".to_string(), + "Test connection".to_string() + ); + + match chat_engine.test_connection().await { + Ok(response) => { + assert!( + response.contains("Connected successfully"), + "Expected connection test response, got: {}", + response + ); + println!("✅ Successfully connected to OpenRouter"); + println!("Response: {}", response); + }, + Err(e) => { + panic!("❌ Failed to connect to OpenRouter: {}\n\ + \nTroubleshooting steps:\n\ + 1. Verify your API key is valid\n\ + 2. Check if you have access to 'anthropic/claude-3-sonnet-20240229'\n\ + 3. Visit https://openrouter.ai/docs#models for supported models\n\ + 4. Check your internet connection\n", + e); + } + } +} + +/// Helper function to run the test with proper environment setup +#[tokio::test] +async fn test_chat_engine_initialization() { + let chat_engine = ChatEngine::new( + "Test system prompt".to_string(), + "Test user prompt".to_string() + ); + + assert_eq!( + chat_engine.test_connection().await.is_ok(), + true, + "Chat engine should initialize and connect successfully" + ); +} From 02db84365016f36f25d7c723103093d0bd4c3d1a Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sat, 14 Dec 2024 10:50:57 -0800 Subject: [PATCH 10/15] chore: update cargo --- Cargo.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b058fc25a2..73aa6cae4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,10 @@ async-openai = "0.14" reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" + +[dev-dependencies] +tokio = { version = "1", features = ["full", "test-util"] } + +[[test]] +name = "integration_test" +path = "tests/integration_test.rs" From 81773695573cf284c03b1d7ad8b6ea9e3943c5b4 Mon Sep 17 00:00:00 2001 From: amit Date: Sat, 14 Dec 2024 12:07:25 -0800 Subject: [PATCH 11/15] fix tests --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..68ec546813 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod chat_engine; + +pub use chat_engine::*; \ No newline at end of file From 1e9cc86de8a1e2db7873f2d01aed2265d5397f55 Mon Sep 17 00:00:00 2001 From: amit Date: Sat, 14 Dec 2024 12:37:32 -0800 Subject: [PATCH 12/15] add test --- tests/integration_test.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/integration_test.rs b/tests/integration_test.rs index f450abfc31..62d6ee8eef 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -6,6 +6,7 @@ use code_forge::ChatEngine; use std::env; +use futures::StreamExt; /// Test the connection to OpenRouter and verify we can access Claude 3 Sonnet #[tokio::test] @@ -56,3 +57,38 @@ async fn test_chat_engine_initialization() { "Chat engine should initialize and connect successfully" ); } + +/// This test will test connection ask a question and get a response and verify the response +#[tokio::test] +async fn test_chat_engine_response() { + let mut chat_engine = ChatEngine::new( + "You are a test assistant.".to_string(), + "Test connection".to_string() + ); + + match chat_engine.test_connection().await { + Ok(_) => { + let mut response = chat_engine.process_message("who is PM of India".to_string()).await; + let mut final_response = String::new(); + while let Some(response_part) = response.next().await { + final_response.push_str(&response_part); + } + println!("Response: {}", final_response); + assert_eq!( + !final_response.is_empty(), + true, + "Expected response from chat engine" + ); + println!("✅ Successfully received response from chat engine"); + }, + Err(e) => { + panic!("❌ Failed to connect to OpenRouter: {}\n\ + \nTroubleshooting steps:\n\ + 1. Verify your API key is valid\n\ + 2. Check if you have access to 'anthropic/claude-3-sonnet-20240229'\n\ + 3. Visit https://openrouter.ai/docs#models for supported models\n\ + 4. Check your internet connection\n", + e); + } + } +} From 01566a817592b2664bcd7ccaf2557272368008a3 Mon Sep 17 00:00:00 2001 From: amit Date: Sat, 14 Dec 2024 18:24:44 -0800 Subject: [PATCH 13/15] add working but ui broken --- src/chat_ui.rs | 217 +++++++++++++++++++++++++++---------------------- src/main.rs | 21 ++--- 2 files changed, 127 insertions(+), 111 deletions(-) diff --git a/src/chat_ui.rs b/src/chat_ui.rs index ffd571f704..706cbc4284 100644 --- a/src/chat_ui.rs +++ b/src/chat_ui.rs @@ -26,12 +26,22 @@ pub struct ChatState { impl ChatState { pub fn new() -> Self { - Self { + let mut state = Self { input: String::new(), cursor_position: 0, messages: Vec::new(), streaming_response: None, - } + }; + + // Add welcome message to initial state + state.messages.push( + "Welcome to Code Forge Chat, powered by Claude 3 Sonnet.\n\ + I'm your AI programming assistant, specializing in software development and technical topics.\n\ + Feel free to ask questions about programming, architecture, best practices, or any tech-related topics.\n\ + Type your message and press Enter to send. Press Ctrl+C or Esc to exit.".to_string() + ); + + state } fn insert_char(&mut self, c: char) { @@ -57,6 +67,24 @@ impl ChatState { self.input.remove(self.cursor_position); } } + + fn start_response(&mut self) { + self.streaming_response = Some(String::from("Assistant: ")); + } + + fn append_to_response(&mut self, text: &str) { + if let Some(ref mut response) = self.streaming_response { + response.push_str(text); + } else { + self.streaming_response = Some(format!("Assistant: {}", text)); + } + } + + fn complete_response(&mut self) { + if let Some(response) = self.streaming_response.take() { + self.messages.push(response); + } + } } pub struct ChatUI { @@ -65,7 +93,7 @@ pub struct ChatUI { } impl ChatUI { - pub fn new() -> Result> { + pub fn new() -> Result> { // Terminal setup enable_raw_mode()?; let mut stdout = io::stdout(); @@ -79,7 +107,7 @@ impl ChatUI { }) } - fn render_chat_ui(frame: &mut Frame, state: &ChatState) -> Result<(), Box> { + fn render_chat_ui(frame: &mut Frame, state: &ChatState) -> Result<(), Box> { let layout = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -89,16 +117,17 @@ impl ChatUI { .split(frame.size()); // Messages area with scrolling - let messages_text = state.messages.iter() - .chain(state.streaming_response.iter()) - .cloned() - .collect::>() - .join("\n"); + let mut display_messages = state.messages.clone(); + if let Some(ref current) = state.streaming_response { + display_messages.push(current.clone()); + } + + let messages_text = display_messages.join("\n"); let messages_block = Paragraph::new(messages_text) .block(Block::default().borders(Borders::ALL).title("Chat Messages")) .wrap(Wrap { trim: true }) - .scroll((state.messages.len() as u16, 0)); + .scroll((display_messages.len().saturating_sub(1) as u16, 0)); frame.render_widget(messages_block, layout[0]); @@ -130,16 +159,69 @@ impl ChatUI { Ok(()) } + async fn handle_key_event(&mut self, key: KeyCode, input_tx: &mpsc::Sender) -> Result> { + let mut state = self.state.lock().await; + match key { + KeyCode::Enter => { + let input = state.input.clone(); + if !input.is_empty() { + state.messages.push(format!("You: {}", input)); + state.start_response(); + state.input.clear(); + state.cursor_position = 0; + input_tx.send(input).await?; + } + }, + KeyCode::Char(c) => { + state.insert_char(c); + }, + KeyCode::Backspace => { + state.backspace(); + }, + KeyCode::Delete => { + state.delete(); + }, + KeyCode::Left => { + if state.cursor_position > 0 { + state.cursor_position -= 1; + } + }, + KeyCode::Right => { + if state.cursor_position < state.input.len() { + state.cursor_position += 1; + } + }, + KeyCode::Home => { + state.cursor_position = 0; + }, + KeyCode::End => { + state.cursor_position = state.input.len(); + }, + KeyCode::Esc => { + return Ok(true); + } + _ => {} + } + + let state_snapshot = state.clone(); + drop(state); + self.terminal.draw(|frame| { + Self::render_chat_ui(frame, &state_snapshot).unwrap() + })?; + + Ok(false) + } + pub async fn run( &mut self, mut response_stream: S, - ) -> Result<(), Box> + input_tx: mpsc::Sender, + ) -> Result<(), Box> where S: Stream + Unpin, { - let (state_tx, mut state_rx) = mpsc::channel(100); - let (input_tx, input_rx) = mpsc::channel(100); let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1); + let (event_tx, mut event_rx) = mpsc::channel(100); // Set up Ctrl+C handler let shutdown_tx_clone = shutdown_tx.clone(); @@ -149,89 +231,43 @@ impl ChatUI { } }); - // Spawn input handling task - let state_clone = Arc::clone(&self.state); - let shutdown_tx_clone = shutdown_tx.clone(); - let input_handle = tokio::spawn(async move { + // Spawn event reading task + let event_tx_clone = event_tx.clone(); + tokio::spawn(async move { loop { - if let Ok(Event::Key(key)) = event::read() { - if key.kind == KeyEventKind::Press { - match key.code { - KeyCode::Enter => { - let mut state = state_clone.lock().await; - let input = state.input.clone(); - if !input.is_empty() { - state.messages.push(format!("You: {}", input)); - let _ = input_tx.send(input).await; - } - state.input.clear(); - state.cursor_position = 0; - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Char(c) => { - let mut state = state_clone.lock().await; - state.insert_char(c); - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Backspace => { - let mut state = state_clone.lock().await; - state.backspace(); - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Delete => { - let mut state = state_clone.lock().await; - state.delete(); - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Left => { - let mut state = state_clone.lock().await; - if state.cursor_position > 0 { - state.cursor_position -= 1; - } - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Right => { - let mut state = state_clone.lock().await; - if state.cursor_position < state.input.len() { - state.cursor_position += 1; - } - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Home => { - let mut state = state_clone.lock().await; - state.cursor_position = 0; - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::End => { - let mut state = state_clone.lock().await; - state.cursor_position = state.input.len(); - drop(state); - let _ = state_tx.send(()).await; - }, - KeyCode::Esc => { - let _ = shutdown_tx_clone.send(()).await; - break; - } - _ => {} - } + if let Ok(event) = event::read() { + if let Err(_) = event_tx_clone.send(event).await { + break; } } } }); - // Process responses + // Initial render + let state_snapshot = self.state.lock().await.clone(); + self.terminal.draw(|frame| { + Self::render_chat_ui(frame, &state_snapshot).unwrap() + })?; + + // Main event loop loop { tokio::select! { + Some(event) = event_rx.recv() => { + if let Event::Key(key) = event { + if key.kind == KeyEventKind::Press { + if self.handle_key_event(key.code, &input_tx).await? { + break; + } + } + } + } Some(response) = response_stream.next() => { let mut state = self.state.lock().await; - state.messages.push(format!("Assistant: {}", response)); + if response == "\n" { + state.complete_response(); + } else { + state.append_to_response(&response); + } let state_snapshot = state.clone(); drop(state); @@ -239,12 +275,6 @@ impl ChatUI { Self::render_chat_ui(frame, &state_snapshot).unwrap() })?; } - Some(_) = state_rx.recv() => { - let state_snapshot = self.state.lock().await.clone(); - self.terminal.draw(|frame| { - Self::render_chat_ui(frame, &state_snapshot).unwrap() - })?; - } Some(_) = shutdown_rx.recv() => { break; } @@ -252,9 +282,6 @@ impl ChatUI { } } - // Clean up input handling task - input_handle.abort(); - Ok(()) } } diff --git a/src/main.rs b/src/main.rs index b7e3d729a6..420debd3fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,11 @@ async fn create_response_stream( tokio::spawn(async move { // Process incoming messages while let Some(input) = input_rx.recv().await { + eprintln!("Processing input in stream: {}", input); let mut response_stream = chat_engine.process_message(input).await; while let Some(response_part) = response_stream.next().await { + eprintln!("Sending response part: {}", response_part); let _ = tx.send(response_part).await; } } @@ -31,7 +33,7 @@ async fn create_response_stream( } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> { // Initialize chat engine let chat_engine = ChatEngine::new( "You are Claude 3 Sonnet, an AI assistant with expertise in programming, software development, and technology. \ @@ -53,27 +55,14 @@ async fn main() -> Result<(), Box> { // Create response stream let response_stream = create_response_stream(chat_engine, input_rx).await; - // Create welcome message - let (welcome_tx, welcome_rx) = mpsc::channel(1); - welcome_tx.send( - "Welcome to Code Forge Chat, powered by Claude 3 Sonnet.\n\ - I'm your AI programming assistant, specializing in software development and technical topics.\n\ - Feel free to ask questions about programming, architecture, best practices, or any tech-related topics.\n\ - Type your message and press Enter to send. Press Ctrl+C or Esc to exit.\n".to_string() - ).await?; - let welcome_stream = ReceiverStream::new(welcome_rx); - - // Combine welcome message with response stream - let combined_stream = welcome_stream.chain(response_stream); - // Start the chat interface - chat_ui.run(combined_stream).await?; + chat_ui.run(response_stream, input_tx).await?; Ok(()) } Err(e) => { eprintln!("🔴 Failed to connect to Claude 3 Sonnet: {}", e); eprintln!("Please check your OPENROUTER_API_KEY environment variable and internet connection."); - Err(e.into()) + Ok(()) } } } From 9ffe24b97e9b1ab141011dd970637da87d266027 Mon Sep 17 00:00:00 2001 From: amit Date: Sat, 14 Dec 2024 18:29:54 -0800 Subject: [PATCH 14/15] add working state --- src/chat_engine.rs | 56 ++++++++++++++++++++++++++++++---------------- src/chat_ui.rs | 15 +++++++++++-- src/main.rs | 2 -- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/chat_engine.rs b/src/chat_engine.rs index 8b973feb78..9f30a42319 100644 --- a/src/chat_engine.rs +++ b/src/chat_engine.rs @@ -57,6 +57,7 @@ impl Config for OpenRouterConfig { } } +#[derive(Clone)] pub struct Agent { system_prompt: String, user_prompt: String, @@ -88,7 +89,7 @@ impl Agent { } /// Test the connection to OpenRouter - pub async fn test_connection(&self) -> Result> { + pub async fn test_connection(&self) -> Result> { let request = CreateChatCompletionRequest { model: MODEL_NAME.to_string(), messages: vec![ @@ -137,30 +138,47 @@ impl Agent { }; let client = self.client.clone(); + let messages = self.messages.clone(); // Spawn task to handle streaming response tokio::spawn(async move { - let mut stream = client.chat().create_stream(request).await.unwrap(); - let mut current_content = String::new(); - - while let Some(result) = stream.next().await { - match result { - Ok(response) => { - if let Some(ref delta) = response.choices[0].delta.content { - current_content.push_str(delta); - let _ = tx.send(delta.to_string()).await; + match client.chat().create_stream(request).await { + Ok(mut stream) => { + let mut current_content = String::new(); + + while let Some(result) = stream.next().await { + match result { + Ok(response) => { + if let Some(ref delta) = response.choices[0].delta.content { + current_content.push_str(delta); + let _ = tx.send(delta.to_string()).await; + } + } + Err(e) => { + eprintln!("Error in stream: {}", e); + let _ = tx.send(format!("Error: {}", e)).await; + break; + } } } - Err(e) => { - let _ = tx.send(format!("Error: {}", e)).await; - break; + + // Add assistant's message to history and send newline to signal completion + if !current_content.is_empty() { + let mut messages = messages; + messages.push( + ChatCompletionRequestMessageArgs::default() + .role(Role::Assistant) + .content(current_content) + .build() + .unwrap() + ); + let _ = tx.send("\n".to_string()).await; } } - } - - // Add assistant's message to history - if !current_content.is_empty() { - let _ = tx.send("\n".to_string()).await; + Err(e) => { + eprintln!("Failed to create stream: {}", e); + let _ = tx.send(format!("Error: {}", e)).await; + } } }); @@ -179,7 +197,7 @@ impl ChatEngine { } } - pub async fn test_connection(&self) -> Result> { + pub async fn test_connection(&self) -> Result> { self.agent.test_connection().await } diff --git a/src/chat_ui.rs b/src/chat_ui.rs index 706cbc4284..039db96697 100644 --- a/src/chat_ui.rs +++ b/src/chat_ui.rs @@ -73,6 +73,11 @@ impl ChatState { } fn append_to_response(&mut self, text: &str) { + // Skip "Sending response part:" messages + if text.starts_with("Sending response part:") { + return; + } + if let Some(ref mut response) = self.streaming_response { response.push_str(text); } else { @@ -82,7 +87,13 @@ impl ChatState { fn complete_response(&mut self) { if let Some(response) = self.streaming_response.take() { - self.messages.push(response); + // Format the response with proper line breaks + let formatted = response + .lines() + .map(|line| line.trim()) + .collect::>() + .join("\n"); + self.messages.push(formatted); } } } @@ -122,7 +133,7 @@ impl ChatUI { display_messages.push(current.clone()); } - let messages_text = display_messages.join("\n"); + let messages_text = display_messages.join("\n\n"); let messages_block = Paragraph::new(messages_text) .block(Block::default().borders(Borders::ALL).title("Chat Messages")) diff --git a/src/main.rs b/src/main.rs index 420debd3fe..96363f45ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,11 +19,9 @@ async fn create_response_stream( tokio::spawn(async move { // Process incoming messages while let Some(input) = input_rx.recv().await { - eprintln!("Processing input in stream: {}", input); let mut response_stream = chat_engine.process_message(input).await; while let Some(response_part) = response_stream.next().await { - eprintln!("Sending response part: {}", response_part); let _ = tx.send(response_part).await; } } From f862d8d40dfd26bbb2e06e2a2fb8ee74d5441b17 Mon Sep 17 00:00:00 2001 From: amit Date: Sun, 15 Dec 2024 14:49:48 -0800 Subject: [PATCH 15/15] fix: scroll issue --- src/chat_ui.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/chat_ui.rs b/src/chat_ui.rs index 039db96697..182574e8da 100644 --- a/src/chat_ui.rs +++ b/src/chat_ui.rs @@ -12,6 +12,7 @@ use ratatui::{ prelude::*, widgets::{Block, Borders, Paragraph, Wrap}, style::{Style, Modifier}, + layout::Margin, }; use tokio::sync::{mpsc, Mutex}; use std::sync::Arc; @@ -118,6 +119,17 @@ impl ChatUI { }) } + fn calculate_wrapped_height(text: &str, width: u16) -> u16 { + let mut height = 0; + for line in text.lines() { + // Calculate how many lines this text will wrap to + let line_length = line.chars().count() as u16; + let wrapped_lines = (line_length + width - 1) / width; + height += wrapped_lines.max(1); // At least one line even if empty + } + height + } + fn render_chat_ui(frame: &mut Frame, state: &ChatState) -> Result<(), Box> { let layout = Layout::default() .direction(Direction::Vertical) @@ -135,12 +147,24 @@ impl ChatUI { let messages_text = display_messages.join("\n\n"); - let messages_block = Paragraph::new(messages_text) - .block(Block::default().borders(Borders::ALL).title("Chat Messages")) + // Get the inner area dimensions accounting for borders + let messages_block = Block::default().borders(Borders::ALL).title("Chat Messages"); + let inner_area = layout[0].inner(&Margin::new(1, 1)); + + // Calculate total content height including message separators + let content_height = Self::calculate_wrapped_height(&messages_text, inner_area.width) + + (display_messages.len().saturating_sub(1) as u16); // Add space for \n\n separators + + // Calculate scroll offset to keep the latest content visible + let viewport_height = inner_area.height; + let scroll_offset = content_height.saturating_sub(viewport_height); + + let messages_widget = Paragraph::new(messages_text) + .block(messages_block) .wrap(Wrap { trim: true }) - .scroll((display_messages.len().saturating_sub(1) as u16, 0)); + .scroll((scroll_offset, 0)); - frame.render_widget(messages_block, layout[0]); + frame.render_widget(messages_widget, layout[0]); // Input area with cursor let input_len = state.input.len();