WIP push-to-talk Letta chat frontend

stub letta completion command

graham.systems a7db3e45 755cb7e7

verified
Changed files
+105
src-tauri
+50
src-tauri/Cargo.lock
··· 1172 1172 ] 1173 1173 1174 1174 [[package]] 1175 + name = "eventsource-stream" 1176 + version = "0.2.3" 1177 + source = "registry+https://github.com/rust-lang/crates.io-index" 1178 + checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" 1179 + dependencies = [ 1180 + "futures-core", 1181 + "nom", 1182 + "pin-project-lite", 1183 + ] 1184 + 1185 + [[package]] 1175 1186 name = "fastrand" 1176 1187 version = "2.3.0" 1177 1188 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1340 1351 version = "0.3.31" 1341 1352 source = "registry+https://github.com/rust-lang/crates.io-index" 1342 1353 checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 1354 + 1355 + [[package]] 1356 + name = "futures-timer" 1357 + version = "3.0.3" 1358 + source = "registry+https://github.com/rust-lang/crates.io-index" 1359 + checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 1343 1360 1344 1361 [[package]] 1345 1362 name = "futures-util" ··· 2374 2391 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 2375 2392 2376 2393 [[package]] 2394 + name = "minimal-lexical" 2395 + version = "0.2.1" 2396 + source = "registry+https://github.com/rust-lang/crates.io-index" 2397 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 2398 + 2399 + [[package]] 2377 2400 name = "miniz_oxide" 2378 2401 version = "0.8.9" 2379 2402 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2403 2426 "futures-util", 2404 2427 "keyring", 2405 2428 "reqwest", 2429 + "reqwest-eventsource", 2406 2430 "serde", 2407 2431 "serde_json", 2408 2432 "strum", ··· 2513 2537 checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 2514 2538 2515 2539 [[package]] 2540 + name = "nom" 2541 + version = "7.1.3" 2542 + source = "registry+https://github.com/rust-lang/crates.io-index" 2543 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 2544 + dependencies = [ 2545 + "memchr", 2546 + "minimal-lexical", 2547 + ] 2548 + 2549 + [[package]] 2516 2550 name = "num-conv" 2517 2551 version = "0.1.0" 2518 2552 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3614 3648 "wasm-streams", 3615 3649 "web-sys", 3616 3650 "webpki-roots", 3651 + ] 3652 + 3653 + [[package]] 3654 + name = "reqwest-eventsource" 3655 + version = "0.6.0" 3656 + source = "registry+https://github.com/rust-lang/crates.io-index" 3657 + checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" 3658 + dependencies = [ 3659 + "eventsource-stream", 3660 + "futures-core", 3661 + "futures-timer", 3662 + "mime", 3663 + "nom", 3664 + "pin-project-lite", 3665 + "reqwest", 3666 + "thiserror 1.0.69", 3617 3667 ] 3618 3668 3619 3669 [[package]]
+1
src-tauri/Cargo.toml
··· 33 33 tauri-plugin-store = "2" 34 34 tauri-plugin-http = "2" 35 35 reqwest = { version = "0.12.23", features = ["json"] } 36 + reqwest-eventsource = "0.6.0" 36 37 37 38 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 38 39 tauri-plugin-positioner = "2"
+10
src-tauri/src/letta/commands.rs
··· 53 53 54 54 Ok(id) 55 55 } 56 + 57 + #[tauri::command] 58 + pub async fn start_llm_completion( 59 + state: tauri::State<'_, AppState>, 60 + message: String, 61 + ) -> Result<(), ()> { 62 + state.letta_manager.start_completion(message).await; 63 + 64 + Ok(()) 65 + }
+43
src-tauri/src/letta/mod.rs
··· 1 1 use std::sync::Arc; 2 2 3 + use futures_util::StreamExt; 3 4 use reqwest::Client; 5 + use reqwest_eventsource::{Event, EventSource}; 4 6 use serde::{Deserialize, Serialize}; 5 7 use serde_json::json; 6 8 use strum::{Display, EnumString}; ··· 89 91 out 90 92 } 91 93 Err(_) => Vec::new(), 94 + } 95 + } 96 + 97 + pub async fn start_completion(&self, msg: String) { 98 + match self 99 + .secrets_manager 100 + .get_secret(crate::secrets::SecretName::LettaApiKey) 101 + { 102 + Ok(api_key) => { 103 + let base_url = self.base_url.lock().await.to_owned(); 104 + let agent_id = self.base_url.lock().await.to_owned(); 105 + let req = self 106 + .http_client 107 + .post(format!("{base_url}/v1/agents/{agent_id}/messages/stream")) 108 + .header("Authorization", format!("Bearer {api_key}")) 109 + .header("Content-Type", "application/json") 110 + .json(&json!({ 111 + "messages": [ 112 + { 113 + "role": "user", 114 + "content": [ msg ] 115 + } 116 + ], 117 + "stream_tokens": true 118 + })); 119 + 120 + let mut source = 121 + EventSource::new(req).expect("could not convert request to event source"); 122 + 123 + while let Some(event) = source.next().await { 124 + match event { 125 + Ok(Event::Open) => println!("stream opened"), 126 + Ok(Event::Message(msg)) => println!("got stream message: {:?}", msg), 127 + Err(err) => { 128 + eprintln!("got stream error: {}", err); 129 + source.close(); 130 + } 131 + } 132 + } 133 + } 134 + Err(err) => eprintln!("could not fetch Letta API key: {}", err), 92 135 } 93 136 } 94 137 }
+1
src-tauri/src/lib.rs
··· 73 73 letta::commands::set_letta_base_url, 74 74 letta::commands::get_letta_agent_id, 75 75 letta::commands::set_letta_agent_id, 76 + letta::commands::start_llm_completion, 76 77 secrets::commands::has_secret, 77 78 secrets::commands::set_secret, 78 79 secrets::commands::delete_secret,