diff --git a/.env.example b/.env.example index 5c6c03b..255850f 100644 --- a/.env.example +++ b/.env.example @@ -3,5 +3,15 @@ MONGODB_PWD= MONGODB_CLSTR=your-cluster.mongodb.net MONGODB_DB_NAME=trippers JWT_SECRET= -GEMINI_API_KEY= UNSPLASH_API_KEY= +AWS_REGION= +AWS_PROFILE= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_CONTAINER_CREDENTIALS_FULL_URI= +AWS_CONTAINER_CREDENTIALS_RELATIVE_URI= +AWS_CONTAINER_AUTHORIZATION_TOKEN= +AWS_EC2_METADATA_DISABLED= +AWS_EC2_METADATA_SERVICE_ENDPOINT= +AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE= +AWS_SDK_UA_APP_ID= diff --git a/.gitignore b/.gitignore index 6022e72..c3f9b80 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ **/*.rs.bk .env + +# Sus files +**/*.br diff --git a/Cargo.lock b/Cargo.lock index 232e97c..f8db7a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,17 +402,17 @@ dependencies = [ "aws-smithy-types", "bytes", "fastrand 2.1.1", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", "hyper 0.14.31", - "hyper-rustls", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", - "rustls", + "rustls 0.21.12", "tokio", "tracing", ] @@ -1259,7 +1259,7 @@ dependencies = [ "tokio-stream", "tokio-util", "tower 0.4.13", - "tower-http", + "tower-http 0.5.2", "tower-layer", "tracing", "tracing-futures", @@ -1596,6 +1596,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "euclid" version = "0.22.11" @@ -1666,6 +1676,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" @@ -2067,6 +2092,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -2300,7 +2344,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2323,6 +2367,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -2331,6 +2376,7 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] @@ -2343,10 +2389,43 @@ dependencies = [ "http 0.2.12", "hyper 0.14.31", "log", - "rustls", + "rustls 0.21.12", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.0", + "hyper-util", + "rustls 0.23.18", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.5.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -2356,13 +2435,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", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -2743,6 +2825,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[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.3" @@ -2888,8 +2976,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", @@ -2901,7 +2989,7 @@ dependencies = [ "take_mut", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "typed-builder", "uuid", @@ -2936,6 +3024,23 @@ dependencies = [ "version_check", ] +[[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 = "nu-ansi-term" version = "0.46.0" @@ -2995,6 +3100,32 @@ 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.87", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -3303,6 +3434,49 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-rustls 0.27.3", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -3359,6 +3533,19 @@ 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" version = "0.21.12" @@ -3367,10 +3554,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -3378,7 +3578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] @@ -3392,6 +3592,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" @@ -3402,6 +3617,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" @@ -3938,6 +4164,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" @@ -3950,6 +4179,27 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "take_mut" version = "0.2.2" @@ -3962,6 +4212,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand 2.1.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.68" @@ -4083,13 +4346,34 @@ dependencies = [ "syn 2.0.87", ] +[[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" 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.18", + "rustls-pki-types", "tokio", ] @@ -4189,6 +4473,20 @@ dependencies = [ "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", + "http 1.1.0", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4290,12 +4588,14 @@ dependencies = [ "aws-sdk-bedrockruntime", "aws-smithy-runtime-api", "aws-smithy-types", + "axum", "axum-extra", "bson", "chrono", "dioxus", "dioxus-free-icons", "dioxus-logger", + "dioxus-web", "dotenv", "futures-util", "getrandom", @@ -4306,9 +4606,11 @@ dependencies = [ "rand", "rand_core", "regex", + "reqwest", "serde", "time", "tokio", + "tower-http 0.6.2", "unsplash-api", "uuid", "web-sys", @@ -4646,6 +4948,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" diff --git a/Cargo.toml b/Cargo.toml index c299722..cb1f519 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,12 +35,16 @@ aws-smithy-types = { version = "1.2.9", optional = true } http-api-isahc-client = { version = "0.2.2", optional = true } unsplash-api = { version = "0.1.0", optional = true } gloo-storage = "0.3.0" +axum = { version = "0.7.7", optional = true } +tower-http = { version = "0.6.1", features = ["cors"], optional = true } +reqwest = { version = "0.12.9", features = ["json"], optional = true } +dioxus-web = { version = "0.5.6", features = ["hydrate"] } # Debug dioxus-logger = "0.5.1" [features] default = [] -server = ["dioxus/axum", "unsplash-api", "http-api-isahc-client", "tokio", "mongodb", "jsonwebtoken", "argon2", "uuid", "rand", "axum-extra", "rand_core", "aws-config", "aws-sdk-bedrockruntime", "aws-smithy-runtime-api", "aws-smithy-types"] +server = ["dioxus/axum", "reqwest", "axum", "tower-http","unsplash-api", "http-api-isahc-client", "tokio", "mongodb", "jsonwebtoken", "argon2", "uuid", "rand", "axum-extra", "rand_core", "aws-config", "aws-sdk-bedrockruntime", "aws-smithy-runtime-api", "aws-smithy-types"] web = ["dioxus/web"] axum-extra = ["dep:axum-extra"] diff --git a/README.md b/README.md index 48d73a1..3869802 100644 --- a/README.md +++ b/README.md @@ -2,285 +2,126 @@ # 📖 Tripper 🤖 -[![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg?logo=rust&logoColor=white)](https://www.rust-lang.org/) +[![Made-with-Rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg?logo=rust&logoColor=white)](https://www.rust-lang.org/) [![Rust](https://img.shields.io/badge/Rust-1.79%2B-blue.svg)](https://www.rust-lang.org) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/wiseaidev) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Open SASS Discord](https://dcbadge.limes.pink/api/server/dGCPR6bq)](https://discord.gg/dGCPR6bq) -![Arch](https://github.com/user-attachments/assets/b5af3f0b-1855-4510-853a-f4258e81cccd) - -| 🐧 Linux `(Recommended)` | 🪟 Windows | -| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| [ ![Linux Banner](https://github.com/user-attachments/assets/9b895bcf-43f8-4839-842b-4ad51c8c7777)](https://github.com/opensass/tripper/releases/download/v0.0.1/dist.zip) | [ ![Windows Banner](https://github.com/user-attachments/assets/9b895bcf-43f8-4839-842b-4ad51c8c7777)](https://github.com/opensass/tripper/releases/download/v0.0.1/dist.zip) | -| `./dist/tripper` | `.\dist\tripper.exe` | +![arch](https://github.com/user-attachments/assets/48a398bc-32fe-4416-975d-ba439a6cddbf) -## 🖥️ For the `.exe` Enjoyers - -So, you're the kinda person who'd rather download an `.exe` than spend 20 minutes watching code compile? No worries; I gotcha! 🎉 Each release comes with pre-compiled binaries. Just download, set env vars, run a command, and boom. - -> [!NOTE] -> -> - 📸 **Unsplash API**: Limited to 50 requests per hour (we're all sharing the same quota, so easy on the trigger!). -> - 💎 **Gemini credits**: Unlimited! So feel free to use as you please. -> - 🗄️ **MongoDB Storage**: Capped at around ~512MB. Let's keep things tidy and not go overboard. - -Now, navigate to the [🔑 Setting Up Env Vars](https://github.com/opensass/tripper#-setting-up-env-vars) section. +## 🚀 About Tripper -## 🤓 For the Hardcore Nerds +Tripper is a modern travel assistant leveraging [**AWS Bedrock**](https://aws.amazon.com/bedrock/) models to enhance your trip planning and exploration. With powerful integrations, streamlined data models, and a modular design, Tripper makes organizing, customizing, and experiencing your journeys effortless. -Aight, if you're, just like me, one of those brave souls who **wants** to compile everything themself, this section is for you. 🛠️ No shortcuts, just raw code and dedication. Grab your favorite terminal, fire up those dependencies, and let the adventure begin! +### 🛠️ Pre-requisites -### 🛠️ Pre-requisites: - -1. Install [`rustup`](https://www.rust-lang.org/tools/install): +1. **Install [`rustup`](https://www.rust-lang.org/tools/install)**: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -1. Install [`Dioxus CLI`](https://dioxuslabs.com/learn/0.5/getting_started): +2. **Install [`Dioxus CLI`](https://dioxuslabs.com/learn/0.5/getting_started)**: ```bash cargo install dioxus-cli ``` -1. Fork/Clone the GitHub repository. +3. **Fork/Clone the Repository**: ```bash git clone https://github.com/opensass/tripper ``` -## 🔑 Setting Up Env Vars +### 🔑 Setting Up Environment Variables -Before you can start running Tripper, you'll need to configure a few environment variables. These are essential for connecting to external services like MongoDB, Unsplash, and the Gemini AI, so let's get you set up! Here's a quick guide: +Before running **Tripper**, configure the environment variables to connect to external services such as **MongoDB**, **Unsplash**, **Google Maps** and **AWS Bedrock**. Here's how: -### Create an `.env` File +#### Create an `.env` File -Inside the project root, copy and create a file named `.env` from `.env-example`. This file will securely store all your environment variables. +Copy the example environment file and update it with your credentials. ```bash cp .env.example .env ``` +**`.env` Variables:** + +```bash +MONGODB_USR= +MONGODB_PWD= +MONGODB_CLSTR=your-cluster.mongodb.net +MONGODB_DB_NAME=tripper +JWT_SECRET= +UNSPLASH_API_KEY= +AWS_REGION= +AWS_PROFILE= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_CONTAINER_CREDENTIALS_FULL_URI= +AWS_CONTAINER_AUTHORIZATION_TOKEN= +AWS_SDK_UA_APP_ID= +``` + > [!NOTE] -> Replace the following values with your actual credentials. -> -> ```bash -> MONGODB_USR= -> MONGODB_PWD= -> MONGODB_CLSTR=your-cluster.mongodb.net -> MONGODB_DB_NAME=trippers -> JWT_SECRET= -> GEMINI_API_KEY= -> UNSPLASH_API_KEY= -> ``` -> -> If you're missing any of these keys, check the service's developer portal to generate them. +> Visit the respective service portals (AWS, MongoDB, Google Maps and Unsplash) to generate any missing credentials. -### 🥑 Set Up MongoDB +### 🥑 MongoDB Setup -Follow [our quick guide](./MongoDB.md) to set up your MongoDB database and connect it to your project! +Follow [this guide](./MongoDB.md) to set up your MongoDB database and establish a connection with Tripper. -### 🔐 Generate JWT Secret Key +### 🔐 Generate a JWT Secret Key -Generate a secret key using OpenSSL and update its env var in the `.env` file. +Use OpenSSL to create a secure JWT secret key and update your `.env` file. ```bash openssl rand -hex 128 - -d8d0b35856c6fa90a8f3f818fa1c71785d63181945077a4c81e28f731de406c94acad5e864fc85604c520cd67e4977a06656eee081d2d0a897415bb42d8dca167662ae53078084ce70feaee104a3428797078c5bb359db277b26182114bb6b6f4e50d34dcce1ab2ed952912f5783ca89138d508f41bc2d56e60ef2480f501819 ``` -### ✨ Gemini AI API +### ✨ Set Up AWS Bedrock -To obtain your API key, navigate to [Google AI Studio](https://aistudio.google.com/app/apikey) and generate it there. This key allows tripper to communicate with Gemini API. +AWS Bedrock provides the AI capabilities that power Tripper's smart recommendations and trip planning features. Ensure your **AWS Bedrock** environment is configured by setting up the required access keys and credentials in your `.env` file. ### 📸 Unsplash API -Tripper uses Unsplash which provides a powerful API to search for and retrieve high-quality images. To communicate with this api you will need a [Secret key](https://unsplash.com/oauth/applications). If you don't already have one, sign up for a free account at Unsplash, create a new app, and copy the Secret key at the bottom of the page after creating the app. - -### 🚀 Building and Running - -- Run the client: - - ```sh - dx serve --port 3000 - ``` - -Navigate to http://localhost:3000 to explore the landing page. - -> [!WARNING] -> This might take a few minutes (yes, seriously). But hey, good things take time, right? - -Happy compiling! 😄 - -## ✅ Supported Features - -- Support for all Gemini models (e.g. Gemini Pro 1.5, Flash 1.5). - -![Gemini Models](https://github.com/user-attachments/assets/58f531d0-c352-40eb-8bb2-aed7359fccbc) - -- Built-in Dark and Light themes. - -![Light Dark Themes](https://github.com/user-attachments/assets/71820497-efcc-4227-a906-e97cdf9aa45b) - -- JWT authentication. - -- Forms validations. - -![Email validation.](https://github.com/user-attachments/assets/7b86a5b5-e5a1-44af-8da1-b442d9869afc) - -- Instant toast notifications when submitting a form. - -![Toast notification.](https://github.com/user-attachments/assets/6c5149c9-bb5d-4786-a51b-38c36b4ade0c) - -- Sending and receiving text messages in real time. - -![Sending and receiving text messages.](https://github.com/user-attachments/assets/d3ca3f38-41dc-4815-b7eb-35f8b5d10e36) - -## 🗂️ Project Structure - -This project is packing 81 files! 😅 But don't worry, it's all organized with love, care, and the principles of SoC and DRY in mind (peak engineering, ngl). Each file has a job to do, and it does it well; like little code ninjas in their own modular worlds. - -Here's what the structure looks like: - -
-❯ cd src && tree - -```sh -❯ cd src && tree -. -├── ai.rs -├── components -│   ├── common -│   │   ├── header.rs -│   │   ├── logo.rs -│   │   └── server.rs -│   ├── common.rs -│   ├── dashboard -│   │   ├── analytics.rs -│   │   ├── trips -│   │   │   ├── create.rs -│   │   │   ├── edit.rs -│   │   │   ├── list.rs -│   │   │   └── read.rs -│   │   ├── trips.rs -│   │   ├── chat -│   │   │   ├── panel.rs -│   │   │   └── sidebar.rs -│   │   ├── chat.rs -│   │   ├── fields -│   │   │   ├── input.rs -│   │   │   ├── number.rs -│   │   │   └── select.rs -│   │   ├── fields.rs -│   │   ├── navbar.rs -│   │   ├── profile.rs -│   │   └── sidebar.rs -│   ├── dashboard.rs -│   ├── features -│   │   ├── grid.rs -│   │   └── item.rs -│   ├── features.rs -│   ├── footer -│   │   ├── bottom.rs -│   │   ├── contact.rs -│   │   ├── icon.rs -│   │   ├── links.rs -│   │   ├── logo.rs -│   │   └── support.rs -│   ├── footer.rs -│   ├── hero.rs -│   ├── navbar -│   │   ├── btns.rs -│   │   └── links.rs -│   ├── navbar.rs -│   ├── pricing.rs -│   ├── spinner.rs -│   ├── testimonial -│   │   ├── author.rs -│   │   ├── card.rs -│   │   └── rating.rs -│   ├── testimonial.rs -│   ├── toast -│   │   ├── manager.rs -│   │   └── provider.rs -│   └── toast.rs -├── components.rs -├── db.rs -├── lib.rs -├── main.rs -├── pages -│   ├── trip.rs -│   ├── dashboard.rs -│   ├── home.rs -│   ├── login.rs -│   └── signup.rs -├── pages.rs -├── router.rs -├── server -│   ├── auth -│   │   ├── controller.rs -│   │   ├── model.rs -│   │   └── response.rs -│   ├── auth.rs -│   ├── trip -│   │   ├── controller.rs -│   │   ├── model.rs -│   │   ├── request.rs -│   │   └── response.rs -│   ├── trip.rs -│   ├── common -│   │   ├── request.rs -│   │   └── response.rs -│   ├── common.rs -│   ├── conversation -│   │   ├── controller.rs -│   │   ├── model.rs -│   │   ├── request.rs -│   │   └── response.rs -│   ├── conversation.rs -│   ├── subscription -│   │   ├── controller.rs -│   │   ├── model.rs -│   │   ├── request.rs -│   │   └── response.rs -│   └── subscription.rs -├── server.rs -├── theme.rs -└── unsplash.rs - -19 directories, 81 files -``` +Tripper integrates with the **Unsplash API** for sourcing high-quality images. Obtain an API key from the [Unsplash Developer Portal](https://unsplash.com/oauth/applications) and include it in your `.env` file. -
+### 🚀 Running the Application -### 🛠️ What's Inside? +1. Start the client: -- **Components**: All modular components live here, following the DRY principle. From `navbar` to `footer`, each feature has its own place, making it easy to find and tweak when needed. -- **Server**: Adheres to the **MVC** pattern, making the backend as clean as a freshly minted Linux distro. You'll find models, controllers, and response handlers for each feature, organized and ready for action. -- **Pages**: Each page of the app (e.g., `dashboard.rs`, `home.rs`) is set up here, so you know exactly where to go to update views. + ```bash + dx serve --port 3000 + ``` -With this structure, the project stays manageable and maintainable, despite those 81 files. Let's be honest, though: it's probably going to keep growing. 😅 +2. Navigate to `http://localhost:3000` to explore the Tripper landing page. -## 👨‍💻 Data Models +> **Note:** The initial build might take a few minutes, but the results are worth the wait! -![MongDB Models](https://github.com/user-attachments/assets/a2f430c3-3d5a-491d-9fc9-b833a555cbc1) +## ✅ Features -Tripper is powered by **MongoDB** storage, with each model carefully structured to keep the app humming along smoothly. Here's a closer look at the data models and how they connect: +- Full support for AWS Bedrock models, including **Claude 3** and other advanced AI solutions. +- Intelligent trip planning with high-quality image integration. +- Secure user authentication and role management. -- **User** 🧑‍💼: Stores user credentials, profiles, and role information. This model ensures each user enjoys secure, authenticated access. -- **Trip** 📚: Contains details like title, type, topics, and handy timestamps for creation and updates, essentially, everything about a trip except the content itself! -- **Detail** 📖: Houses the content for each detail, stored in both markdown and HTML formats for flexibility. -- **Conversation** 💬: Logs chats between users and the Gemini AI, so each interaction has a place in history. -- **Message** 📝: Tracks individual messages within each conversation, capturing the ebb and flow of the AI interaction. -- **Subscription** 💳: Manages subscription plans, payment methods, and active status, essentially the gatekeeper for access levels and perks. +## 🛠️ Project Structure -> [!NOTE] -> MongoDB allows us to embed entire documents within another document, bypassing the need for an `ID` relationship (though it does add one more DB call if we want to fetch the data separately). For now, we're not hitting any performance bottlenecks, but this option keeps things flexible as we scale. +- **Components**: Reusable UI components like `navbar` and `footer` ensure consistency and maintainability. +- **Server**: Organized with the **MVC** pattern for clear separation of concerns. This includes models, controllers, and response handlers. +- **Pages**: All app views (e.g., `dashboard`, `home`) are modularized for straightforward updates. + +This structure keeps the project scalable and easy to navigate as it grows. + +## 👨‍💻 Data Models + +Tripper uses **MongoDB** for data storage, with well-defined models for efficiency and scalability: -Each model is designed to keep data tightly organized, minimize dependencies, and allow for easy scaling. So whether it's a quick query for a single user or a deep dive into chat history, these models keep Tripper streamlined and ready to grow! 🚀 +- **User** 🧑‍💼: Manages user credentials, profiles, and roles for secure access. +- **Trip** 📚: Tracks trip details such as title, type, topics, and timestamps. +- **Detail** 📖: Stores trip daily details content in both markdown and HTML formats for flexibility. +- **Conversation** 💬: Records AI interactions for reference and analysis. +- **Message** 📝: Logs individual messages in conversations for traceability. diff --git a/src/components/dashboard/chat/panel.rs b/src/components/dashboard/chat/panel.rs index 81e93b6..c90f765 100644 --- a/src/components/dashboard/chat/panel.rs +++ b/src/components/dashboard/chat/panel.rs @@ -193,12 +193,12 @@ pub fn ChatPanel(conversation_id: Signal, user_token: Signal) messages.set(current_messages); // spawn(async move { - // let response = send_query_to_gemini(SendQueryRequest { + // let response = send_query_to_bedrock(SendQueryRequest { // query: query_text, // trip: trip.id.to_string(), // detail: detail.id.to_string(), // conversation_id: conversation_id(), - // model: "gemini-pro".to_string(), + // model: "anthropic.claude-3-haiku-20240307-v1:0".to_string(), // token: user_token(), // }) // .await; diff --git a/src/components/dashboard/navbar.rs b/src/components/dashboard/navbar.rs index 3be2e8e..b3f6cb3 100644 --- a/src/components/dashboard/navbar.rs +++ b/src/components/dashboard/navbar.rs @@ -36,7 +36,7 @@ pub fn Navbar(dark_mode: bool) -> Element { class: format!("p-2 rounded-full flex items-center justify-center {}", if dark_mode { "bg-gray-700" } else { "bg-gray-200" }), onclick: move |_| show_dropdown.set(!show_dropdown()), img { - src: "./features.webp", + src: "https://rustacean.net/assets/rustacean-flat-happy.svg", alt: "User profile image", class: "w-8 h-8 rounded-full" } diff --git a/src/components/dashboard/trips/create.rs b/src/components/dashboard/trips/create.rs index 16ce24e..10ed110 100644 --- a/src/components/dashboard/trips/create.rs +++ b/src/components/dashboard/trips/create.rs @@ -7,6 +7,7 @@ use crate::components::spinner::Spinner; use crate::components::spinner::SpinnerSize; use crate::components::toast::manager::ToastManager; use crate::components::toast::manager::ToastType; +use crate::server::trip::controller::fetch_google_places_autocomplete; use crate::server::trip::controller::generate_detail_content; use crate::server::trip::controller::generate_trip_outline; use crate::server::trip::request::GenerateDetailContentRequest; @@ -17,38 +18,87 @@ use chrono::Duration; use chrono::Utc; use dioxus::prelude::*; use gloo_storage::{LocalStorage, Storage}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct GooglePlacesResponse { + predictions: Vec, +} + +#[derive(Deserialize)] +struct Prediction { + description: String, + place_id: String, +} #[component] pub fn CreateTripPanel(user_token: Signal) -> Element { let dark_mode = *THEME.read() == Theme::Dark; let title = use_signal(|| "".to_string()); - let subtitle = use_signal(|| "".to_string()); - let model = use_signal(|| "gemini-1.5-flash".to_string()); + let model = use_signal(|| "anthropic.claude-3-haiku-20240307-v1:0".to_string()); let subtopics = use_signal(|| 30); let details = use_signal(|| 5); let language = use_signal(|| "English".to_string()); let max_length = use_signal(|| 10); + let api_key = use_signal(|| "google_api_key".to_string()); let title_valid = use_signal(|| true); - let subtitle_valid = use_signal(|| true); + let destination_valid = use_signal(|| true); let language_valid = use_signal(|| true); let mut loading = use_signal(|| false); let _form_error = use_signal(|| None::); let validate_title = |title: &str| !title.is_empty(); - let validate_subtitle = |subtitle: &str| !subtitle.is_empty(); + let validate_destination = |destination: &str| !destination.is_empty(); let validate_language = |language: &str| !language.is_empty(); let mut toasts_manager = use_context::>(); + let mut recommended_destinations = use_signal(|| vec![]); + let mut destination = use_signal(|| "".to_string()); + let mut selected_destination = use_signal(|| Some("Beirut, Lebanon".to_string())); + + let mut handle_destination_select = move |selected: String| { + selected_destination.set(Some(selected.clone())); + destination.set(selected); + recommended_destinations.set(vec![]); + }; + let mut fetch_recommendations = { + move |input: String| { + if input.is_empty() { + recommended_destinations.set(vec![]); + return; + } + + spawn(async move { + match fetch_google_places_autocomplete(input, api_key()).await { + Ok(response) => { + let suggestions: Vec = response + .predictions + .iter() + .map(|p| p.description.clone()) + .collect(); + recommended_destinations.set(suggestions); + } + Err(_) => { + recommended_destinations.set(vec![]); + } + } + }); + } + }; + + let handle_destination_input = move |e: Event| { + let input = e.value(); + destination.set(input.clone()); + fetch_recommendations(input); + }; + let handle_submit = move |e: Event| { e.stop_propagation(); - let title_value = title().clone(); - let subtitle_value = subtitle().clone(); loading.set(true); - if !validate_title(&title_value) || !validate_subtitle(&subtitle_value) { - // form_error.set(Some("Title and subtitle are required.".to_string())); + if !validate_title(&title()) || !validate_destination(&destination()) { toasts_manager.set( toasts_manager() .add_toast( @@ -68,7 +118,7 @@ pub fn CreateTripPanel(user_token: Signal) -> Element { match generate_trip_outline(GenerateTripRequest { title: title(), token: user_token(), - subtitle: subtitle(), + subtitle: selected_destination().expect("destination"), model: model(), subtopics: subtopics(), details: details(), @@ -101,7 +151,7 @@ pub fn CreateTripPanel(user_token: Signal) -> Element { toasts_manager() .add_toast( "Info".into(), - "Generating details content...".into(), + "Generating Trip Daily Plans...".into(), ToastType::Info, Some(Duration::seconds(5)), ) @@ -131,7 +181,6 @@ pub fn CreateTripPanel(user_token: Signal) -> Element { loading.set(false); } Err(e) => { - // form_error.set(Some(format!("Failed to store trip: {}", e))); let msg = e.to_string(); let error_message = msg .splitn(2, "error running server function:") @@ -154,7 +203,6 @@ pub fn CreateTripPanel(user_token: Signal) -> Element { } } Err(e) => { - // form_error.set(Some(format!("Failed to generate content: {}", e))); let msg = e.to_string(); let error_message = msg .splitn(2, "error running server function:") @@ -180,32 +228,84 @@ pub fn CreateTripPanel(user_token: Signal) -> Element { }; rsx! { - div { class: format!("p-4 {}", if dark_mode { "bg-gray-800 text-white" } else { "bg-white text-gray-900" }), - h2 { class: "text-xl font-semibold mb-4", "Generate" } - form { class: "space-y-4", - onsubmit: handle_submit, - InputField { label: "Title", value: title, is_valid: title_valid, validate: validate_title, required: true } - InputField { label: "Destination", value: subtitle, is_valid: subtitle_valid, validate: validate_subtitle, required: true } - SelectField { label: "Model", options: vec!["claude-3", "claude-3.5-sonet"], selected: model } - NumberField { label: "Budget ($)", value: subtopics, required: true } - InputField { label: "Language", value: language, is_valid: language_valid, validate: validate_language, required: true } - NumberField { label: "NB Days", value: max_length, required: true } - // if let Some(error) = &form_error() { - // p { class: "text-red-600", "{error}" } - // } - button { - class: format!("flex items-center space-x-2 bg-blue-500 text-white px-4 py-2 rounded {}", if dark_mode { "bg-blue-600" } else { "" }), - r#type: "submit", - disabled: loading(), - if loading() { - Spinner { - aria_label: "Loading spinner".to_string(), - size: SpinnerSize::Md, - dark_mode: true, + div { + class: format!("flex p-4 flex-col lg:flex-row {}", + if dark_mode { "bg-gray-800 text-white" } else { "bg-white text-gray-900" } + ), + div { + h2 { class: "text-xl font-semibold mb-4", "Plan A Trip" } + form { + class: "space-y-4 flex-1", + onsubmit: handle_submit, + + InputField { label: "Title", value: title, is_valid: title_valid, validate: validate_title, required: true } + + div { class: "relative", + div { + label { + class: format!("block text-sm font-medium {}", if dark_mode { "text-gray-300" } else { "text-gray-700" }), + "Destination" + } + input { + class: format!( + "mt-1 block w-full p-2 border rounded-md shadow-sm {} {}", + if dark_mode { "bg-gray-900" } else { "" }, + if destination_valid() { "border-gray-300" } else { "border-red-500" } + ), + value: "{destination}", + placeholder: "Enter a destination", + oninput: handle_destination_input, + required: true + } + if !destination_valid() { + p { class: "text-red-500 text-sm mt-1", "Invalid input" } + } } - span { "Generating..." } - } else { - span { "Generate" } + if !recommended_destinations().is_empty() { + ul { class: format!("absolute shadow-lg z-10 border rounded {}", if dark_mode { "bg-gray-900" } else { "bg-white" }), + for dest in recommended_destinations() { + li { + class: "p-2 hover:bg-gray-200 cursor-pointer", + onclick: move |_| handle_destination_select(dest.clone()), + "{dest}" + } + } + } + } + } + + SelectField { label: "Model", options: vec!["claude-3", "claude-3.5-sonet"], selected: model } + NumberField { label: "Budget ($)", value: subtopics, required: true } + InputField { label: "Language", value: language, is_valid: language_valid, validate: validate_language, required: true } + NumberField { label: "NB Days", value: max_length, required: true } + + button { + class: format!("flex items-center space-x-2 bg-blue-500 text-white px-4 py-2 rounded {}", if dark_mode { "bg-blue-600" } else { "" }), + r#type: "submit", + disabled: loading(), + if loading() { + Spinner { + aria_label: "Loading spinner".to_string(), + size: SpinnerSize::Md, + dark_mode: true, + } + span { "Generating..." } + } else { + span { "Generate" } + } + } + } + + } + + if let Some(destination) = selected_destination() { + div { + class: "mt-4 lg:mt-0 lg:ml-8 flex-1", + h2 { class: "text-xl font-semibold mb-4", "Google Maps Preview" } + iframe { + src: format!("https://www.google.com/maps/embed/v1/place?key={}&q={}", api_key(), destination.replace(" ", "+")), + class: "w-full h-64 border-0 h-4/5", + allowfullscreen: "true", } } } diff --git a/src/components/dashboard/trips/list.rs b/src/components/dashboard/trips/list.rs index 865cfd2..69fa895 100644 --- a/src/components/dashboard/trips/list.rs +++ b/src/components/dashboard/trips/list.rs @@ -123,6 +123,10 @@ pub fn TripsPanel(user_token: Signal) -> Element { "p-4 shadow rounded-lg {}", if dark_mode { "bg-gray-700" } else { "bg-gray-100" } ), + p { + class: "mt-2 text-sm text-gray-700", + "{trip.subtitle.expect(\"REASON\")}" + } img { src: trip.cover.as_deref().unwrap_or("/path/to/default-cover.jpg"), alt: "Trip cover", diff --git a/src/components/features.rs b/src/components/features.rs index 735107b..8acbc1e 100644 --- a/src/components/features.rs +++ b/src/components/features.rs @@ -65,7 +65,7 @@ pub fn Features() -> Element { div { class: "relative mb-12 space-y-6", img { - src: "./features-icon.webp", + src: "./features.webp", alt: "Tripper Icon", class: "w-24 h-24 mx-auto animate-spin-slow hover:animate-spin" } diff --git a/src/main.rs b/src/main.rs index 5cb16b1..eae93a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,21 +2,68 @@ use dioxus::prelude::*; use dioxus_logger::tracing; -use dotenv::dotenv; use tripper::components::toast::provider::ToastProvider; use tripper::router::Route; fn main() { - dotenv().ok(); - dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger"); - tracing::info!("starting app"); - launch(App); + #[cfg(feature = "web")] + { + dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger"); + tracing::info!("starting app"); + let config = dioxus_web::Config::new().hydrate(true); + + LaunchBuilder::new().with_cfg(config).launch(App); + } + + #[cfg(feature = "server")] + { + use axum::http::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}; + use axum::http::Method; + use axum::{Extension, Router}; + use dotenv::dotenv; + use std::sync::Arc; + use tower_http::cors::{Any, CorsLayer}; + + dotenv().ok(); + dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger"); + + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + let cors = CorsLayer::new() + .allow_origin(Any) + // TODO + // .allow_origin("http://0.0.0.0:3000".parse::().unwrap()) + // .allow_origin( + // "https://generativelanguage.googleapis.com" + // .parse::() + // .unwrap(), + // ) + .allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE]) + // .allow_credentials(true) + .allow_headers([AUTHORIZATION, ACCEPT, CONTENT_TYPE]); + + let app = Router::new() + .layer(cors) + .serve_dioxus_application(ServeConfig::builder().build(), || { + VirtualDom::new(App) + }) + .await; + + let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 3000)); + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + }); + } } fn App() -> Element { rsx! { - ToastProvider { - Router:: {} + ToastProvider { + Router:: {} } } } diff --git a/src/server/conversation/controller.rs b/src/server/conversation/controller.rs index 973f3e2..bb048a1 100644 --- a/src/server/conversation/controller.rs +++ b/src/server/conversation/controller.rs @@ -262,7 +262,7 @@ pub async fn send_query_to_bedrock( let response_message = Message { id: ObjectId::new(), conversation: req.conversation_id, - sender: "gemini".to_string(), + sender: "bedrock".to_string(), content: text.clone(), timestamp: Utc::now(), }; diff --git a/src/server/trip/controller.rs b/src/server/trip/controller.rs index 25a6e96..2e61a03 100644 --- a/src/server/trip/controller.rs +++ b/src/server/trip/controller.rs @@ -55,6 +55,20 @@ use aws_sdk_bedrockruntime::{ types::{ContentBlock, ConversationRole, Message as BedrockMessage}, Client, }; +#[cfg(feature = "server")] +use reqwest::Client as ReqClient; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct GooglePlacesResponse { + pub predictions: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Prediction { + pub description: String, + pub place_id: String, +} #[server] pub async fn store_trip( @@ -410,6 +424,8 @@ pub async fn generate_detail_content( let mut markdown = "".to_string(); + let mut html = "".to_string(); + let response = client .converse() .model_id("anthropic.claude-3-haiku-20240307-v1:0") @@ -451,8 +467,6 @@ pub async fn generate_detail_content( language = req.language, ); - let mut html = "".to_string(); - let response = client .converse() .model_id("anthropic.claude-3-haiku-20240307-v1:0") @@ -710,3 +724,29 @@ pub async fn get_details_for_trip( data: details, }) } + +#[server] +pub async fn fetch_google_places_autocomplete( + input: String, + api_key: String, +) -> Result { + let url = format!( + "https://maps.googleapis.com/maps/api/place/autocomplete/json?input={}&key={}", + input, api_key + ); + + let client = ReqClient::new(); + + let response = client + .get(&url) + .send() + .await + .map_err(|_| ServerFnError::new("Error fetching autocomplete data from Google API"))?; + + let google_response = response + .json::() + .await + .map_err(|_| ServerFnError::new("Error parsing response from Google API"))?; + + Ok(google_response) +}