diff --git a/.gitignore b/.gitignore index 6022e72..bfd734f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ **/*.rs.bk .env + +# Sus files +**/*.br \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 718ae36..8dff5ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,7 @@ dependencies = [ "axum", "axum-extra", "bson", + "bytes", "chrono", "dioxus", "dioxus-free-icons", @@ -61,12 +62,15 @@ dependencies = [ "http-api-isahc-client", "jsonwebtoken", "mongodb", + "quote", "rand", "rand_core", "regex", + "reqwest", "serde", "time", "tokio", + "tower-http 0.6.1", "unsplash-api", "uuid", "web-sys", @@ -194,9 +198,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body", "http-body-util", - "hyper 1.5.0", + "hyper", "hyper-util", "itoa", "matchit", @@ -230,7 +234,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -253,7 +257,7 @@ dependencies = [ "cookie", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -917,7 +921,7 @@ dependencies = [ "dioxus_server_macro", "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper", "once_cell", "pin-project", "serde", @@ -928,7 +932,7 @@ dependencies = [ "tokio-stream", "tokio-util", "tower 0.4.13", - "tower-http", + "tower-http 0.5.2", "tower-layer", "tracing", "tracing-futures", @@ -1485,9 +1489,9 @@ dependencies = [ [[package]] name = "gems" -version = "0.0.7" +version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff15c7c4ac2b96ce67842335998c44e9e57ace31c527b7bd01098d443dda7d2e" +checksum = "bf96cec183217bdb2acde6cd7a6e95f57d531b85ade09e3c101c4fd7fb283ce6" dependencies = [ "base64 0.22.1", "futures-util", @@ -1752,16 +1756,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http 0.2.12", + "http 1.1.0", "indexmap 2.6.0", "slab", "tokio", @@ -1940,17 +1944,6 @@ dependencies = [ "isahc", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1970,7 +1963,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body", "pin-project-lite", ] @@ -1994,58 +1987,56 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.31" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", - "http 0.2.12", - "http-body 0.4.6", + "http 1.1.0", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] -name = "hyper" -version = "1.5.0" +name = "hyper-rustls" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ - "bytes", - "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.1", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", + "hyper", + "hyper-util", + "rustls 0.23.16", + "rustls-pki-types", "tokio", + "tokio-rustls 0.26.0", + "tower-service", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.31", + "http-body-util", + "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -2055,13 +2046,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.1", - "hyper 1.5.0", + "http-body", + "hyper", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -2593,8 +2587,8 @@ dependencies = [ "percent-encoding", "rand", "rustc_version_runtime", - "rustls", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_bytes", "serde_with", @@ -2606,7 +2600,7 @@ dependencies = [ "take_mut", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "typed-builder", "uuid", @@ -3041,20 +3035,23 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.31", + "http 1.1.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -3063,11 +3060,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -3078,7 +3075,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -3158,10 +3155,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3171,6 +3181,21 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -3181,6 +3206,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.18" @@ -3450,7 +3486,7 @@ dependencies = [ "gloo-net 0.6.0", "http 1.1.0", "http-body-util", - "hyper 1.5.0", + "hyper", "inventory", "js-sys", "once_cell", @@ -3717,6 +3753,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -3731,20 +3770,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -3912,7 +3951,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.16", + "rustls-pki-types", "tokio", ] @@ -3997,7 +4047,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body", "http-body-util", "http-range-header", "httpdate", @@ -4012,6 +4062,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.1.0", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4429,6 +4493,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4680,6 +4774,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 2e8fa97..480d54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ mongodb = { version = "3.1.0", optional = true } dotenv = { version = "0.15.0" } chrono = { version = "0.4.38", features = ["serde"] } bson = { version = "2.13.0", features = ["chrono-0_4"] } -futures-util = { version = "0.3.31" } +futures-util = "0.3.31" jsonwebtoken = { version = "9.3.0", optional = true } argon2 = { version = "0.5.3", optional = true } tokio = { version = "1.41.1", optional = true } @@ -26,19 +26,23 @@ anyhow = "1.0.93" time = "0.3.36" regex = "1.11.1" getrandom = { version = "0.2.15", features = ["js"] } -gems = { version = "0.0.7", optional = true } +gems = { version = "0.0.8", optional = true } http-api-isahc-client = { version = "0.2.2", optional = true } axum = { version = "0.7.7", optional = true } unsplash-api = { version = "0.1.0", optional = true } gloo-storage = "0.3.0" - -# Debug -dioxus-logger = "0.5.1" dioxus-free-icons = { version = "0.8.6", features = ["font-awesome-regular", "font-awesome-brands", "font-awesome-solid"] } web-sys = { version = "0.3.72", features = ["Selection", "Window"] } dioxus-web = { version = "0.5.6", features = ["hydrate"], optional = true } +tower-http = { version = "0.6.1", features = ["cors"], optional = true } + +# Debug +dioxus-logger = "0.5.1" +reqwest = "0.12.9" +quote = "1.0.37" +bytes = "1.8.0" [features] default = [] -server = ["dioxus/axum", "axum", "unsplash-api", "http-api-isahc-client", "tokio", "mongodb", "jsonwebtoken", "argon2", "uuid", "rand", "axum-extra", "rand_core", "gems"] -web = ["dioxus/web", "dioxus-web"] \ No newline at end of file +server = ["dioxus/axum", "axum", "unsplash-api", "tower-http", "http-api-isahc-client", "tokio", "mongodb", "jsonwebtoken", "argon2", "uuid", "rand", "axum-extra", "rand_core", "gems"] +web = ["dioxus/web", "dioxus-web"] diff --git a/src/components/dashboard/chat/panel.rs b/src/components/dashboard/chat/panel.rs index dc4709e..6219a4c 100644 --- a/src/components/dashboard/chat/panel.rs +++ b/src/components/dashboard/chat/panel.rs @@ -16,6 +16,7 @@ use crate::server::conversation::controller::send_query_to_gemini; use crate::server::conversation::model::Message; use crate::server::conversation::request::GetMessagesRequest; use crate::server::conversation::request::SendQueryRequest; +use futures_util::StreamExt; use gloo_storage::Storage; use crate::theme::Theme; @@ -43,6 +44,17 @@ fn truncate(text: String, max_length: usize) -> String { } } +pub fn extract_text_from_partial_json(partial_json: &str) -> Option { + if let Some(start_index) = partial_json.find("\"text\": \"") { + if let Some(end_index) = partial_json[start_index + "\"text\": \"".len()..].find('\"') { + let text_value = &partial_json[start_index + "\"text\": \"".len() + ..start_index + "\"text\": \"".len() + end_index]; + return Some(text_value.to_owned()); + } + } + None +} + #[component] pub fn ChatPanel(conversation_id: Signal, user_token: Signal) -> Element { let mut messages = use_signal(Vec::::new); @@ -206,11 +218,67 @@ pub fn ChatPanel(conversation_id: Signal, user_token: Signal) .await; match response { - Ok(resp_message) => { - let mut current_messages = messages(); - current_messages.push(resp_message.data); - thinking.set(false); - messages.set(current_messages); + Ok(mut resp_stream) => { + let mut current_messages = messages().clone(); + let mut message: String = Default::default(); + let message_id = ObjectId::new(); + + let response_message = Message { + id: message_id, + conversation: conversation_id(), + sender: "gemini".to_string(), + content: message.clone(), + timestamp: Utc::now(), + }; + current_messages.push(response_message); + messages.set(current_messages.clone()); + + while let Some(mut chunk) = resp_stream + .data + .as_mut() + .expect("stream object") + .next() + .await + { + if let Ok(parsed_json) = + std::str::from_utf8(chunk.as_mut().unwrap()) + { + if let Some(text_value) = + extract_text_from_partial_json(parsed_json) + { + let lines: Vec<&str> = text_value + .split("\\n") + .flat_map(|s| s.split('\n')) + .collect(); + + for line in lines { + message.push_str(&line.replace('\\', "")); + + if let Some(existing_message) = current_messages + .clone() + .iter_mut() + .find(|m| m.id == message_id) + { + existing_message.content = message.clone(); + } + + thinking.set(false); + messages.set(current_messages.clone()); + } + } + } else { + // eprintln!("Failed to parse chunk: {:?}", chunk.as_ref().unwrap()); + } + } + + let response_message = Message { + id: ObjectId::new(), + conversation: conversation_id(), + sender: "gemini".to_string(), + content: message, + timestamp: Utc::now(), + }; + let _ = save_message_to_db(response_message).await; } Err(err) => { dioxus_logger::tracing::error!("{:?}", err); diff --git a/src/main.rs b/src/main.rs index 738d19a..361d548 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,6 @@ fn main() { #[cfg(feature = "web")] { let config = dioxus_web::Config::new().hydrate(true); - - #[cfg(feature = "web")] LaunchBuilder::new().with_cfg(config).launch(App); } @@ -25,6 +23,7 @@ fn main() { use aibook::db::get_client; use axum::{Extension, Router}; use std::sync::Arc; + use tower_http::cors::CorsLayer; #[derive(Clone)] #[allow(dead_code)] @@ -42,11 +41,12 @@ fn main() { }); let app = Router::new() + .layer(CorsLayer::permissive()) + .layer(Extension(state)) .serve_dioxus_application(ServeConfig::builder().build(), || { VirtualDom::new(App) }) - .await - .layer(Extension(state)); + .await; let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 3000)); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); diff --git a/src/server/conversation/controller.rs b/src/server/conversation/controller.rs index 5dff319..3106bfb 100644 --- a/src/server/conversation/controller.rs +++ b/src/server/conversation/controller.rs @@ -198,30 +198,13 @@ pub async fn send_query_to_gemini(req: SendQueryRequest) -> Result, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct MessageResponse { pub status: String, - pub data: Message, + + #[serde(skip)] + pub data: Option> + Send>>>, +} + +impl Default for MessageResponse { + fn default() -> Self { + Self { + status: "success".to_string(), + data: Some(Box::pin(stream::empty())), + } + } }