WIP push-to-talk Letta chat frontend
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 149 lines 4.7 kB view raw
1use std::sync::Arc; 2 3use futures_util::StreamExt; 4use reqwest::Client; 5use reqwest_eventsource::{Event, EventSource}; 6use serde_json::{from_str, json}; 7use tauri::async_runtime::{channel, spawn, Mutex, Receiver, Sender}; 8use tauri::Wry; 9use tauri_plugin_store::Store; 10 11use crate::secrets::SecretsManager; 12use types::{LettaAgentInfo, LettaCompletionMessage, LettaConfigKey, LettaError}; 13 14pub mod commands; 15pub mod types; 16 17pub struct LettaManager { 18 http_client: Client, 19 secrets_manager: Arc<SecretsManager>, 20 store: Arc<Store<Wry>>, 21 base_url: Mutex<String>, 22 agent_id: Mutex<String>, 23} 24impl LettaManager { 25 pub fn new( 26 http_client: Client, 27 store: Arc<Store<Wry>>, 28 secrets_manager: Arc<SecretsManager>, 29 ) -> Self { 30 let base_url = match store.get(LettaConfigKey::BaseUrl.to_string()) { 31 Some(url) => url 32 .as_str() 33 .expect("Letta base URL in config is not a string") 34 .to_owned(), 35 None => { 36 let url = "https://api.letta.com".to_owned(); 37 38 store.set(LettaConfigKey::BaseUrl.to_string(), json!(url)); 39 40 url 41 } 42 }; 43 let agent_id = match store.get(LettaConfigKey::AgentId.to_string()) { 44 Some(id) => id 45 .as_str() 46 .expect("Letta agent ID in config is not a string") 47 .to_owned(), 48 None => "".to_owned(), 49 }; 50 51 Self { 52 http_client, 53 secrets_manager, 54 store, 55 base_url: Mutex::new(base_url), 56 agent_id: Mutex::new(agent_id), 57 } 58 } 59 60 pub async fn list_agents(&self) -> Result<Vec<LettaAgentInfo>, LettaError> { 61 let api_key = self 62 .secrets_manager 63 .get_secret(crate::secrets::SecretName::LettaApiKey)?; 64 let base_url = self.base_url.lock().await.to_owned(); 65 let req = self 66 .http_client 67 .get(format!("{base_url}/v1/agents/")) 68 .header("Authorization", format!("Bearer {api_key}")); 69 70 let res = req.send().await.expect("failed to list Letta agents"); 71 let out = res.json::<Vec<LettaAgentInfo>>().await?; 72 73 Ok(out) 74 } 75 76 pub async fn start_completion( 77 &self, 78 msg: String, 79 ) -> Result<Receiver<LettaCompletionMessage>, LettaError> { 80 let api_key = self 81 .secrets_manager 82 .get_secret(crate::secrets::SecretName::LettaApiKey)?; 83 let base_url = self.base_url.lock().await.to_owned(); 84 let agent_id = self.agent_id.lock().await.to_owned(); 85 let body = &json!({ 86 "messages": [ 87 { 88 "role": "user", 89 "content": [{ 90 "type": "text", 91 "text": msg, 92 }] 93 } 94 ], 95 "stream_tokens": true 96 }); 97 98 println!("body: {:?}", body); 99 100 let req = self 101 .http_client 102 .post(format!("{base_url}/v1/agents/{agent_id}/messages/stream")) 103 .header("Authorization", format!("Bearer {api_key}")) 104 .header("Content-Type", "application/json") 105 .json(body); 106 107 let source = EventSource::new(req).expect("failed to clone request"); 108 109 let (tx, rx) = channel::<LettaCompletionMessage>(100); 110 let handler = handle_completion_messages(source, tx); 111 112 spawn(handler); 113 114 return Ok(rx); 115 } 116} 117 118async fn handle_completion_messages( 119 mut source: EventSource, 120 sender: Sender<LettaCompletionMessage>, 121) { 122 while let Some(event) = source.next().await { 123 match event { 124 Ok(Event::Open) => println!("stream opened"), 125 Ok(Event::Message(msg)) => { 126 if msg.data == "[DONE]" { 127 continue; 128 }; 129 130 match from_str::<LettaCompletionMessage>(&msg.data) { 131 Ok(content) => { 132 sender.send(content).await.expect("failed to forward event"); 133 } 134 Err(err) => { 135 eprintln!( 136 "failed to parse message: {:?}, err: {}", 137 msg.data.clone(), 138 err 139 ) 140 } 141 } 142 } 143 Err(err) => { 144 eprintln!("got stream error: {}", err); 145 source.close(); 146 } 147 } 148 } 149}