diff --git a/deno.lock b/deno.lock index bcbf55c..4d72536 100644 --- a/deno.lock +++ b/deno.lock @@ -4,6 +4,7 @@ "jsr:@std/async@^1.0.14": "1.0.14", "npm:@deno/vite-plugin@^1.0.5": "1.0.5_vite@6.3.5__picomatch@4.0.3", "npm:@fontsource-variable/recursive@^5.2.7": "5.2.7", + "npm:@fontsource/flow-block@^5.2.7": "5.2.7", "npm:@iconify/json@^2.2.382": "2.2.382", "npm:@iconify/tailwind4@^1.0.6": "1.0.6_tailwindcss@4.1.12", "npm:@sveltejs/adapter-static@^3.0.6": "3.0.9_@sveltejs+kit@2.36.2__@sveltejs+vite-plugin-svelte@5.1.1___svelte@5.38.3____acorn@8.15.0___vite@6.3.5____picomatch@4.0.3__svelte@5.38.3___acorn@8.15.0__vite@6.3.5___picomatch@4.0.3__acorn@8.15.0_@sveltejs+vite-plugin-svelte@5.1.1__svelte@5.38.3___acorn@8.15.0__vite@6.3.5___picomatch@4.0.3_svelte@5.38.3__acorn@8.15.0_vite@6.3.5__picomatch@4.0.3", @@ -198,6 +199,9 @@ "@fontsource-variable/recursive@5.2.7": { "integrity": "sha512-6hDJ9EDUxShoqhmxV3B+q8k6YC7cBBzVvDapNAESDIn53SzBC7y1KbEr/aha70zx/GWJiNTTVf7gdJ3B5K1/vg==" }, + "@fontsource/flow-block@5.2.7": { + "integrity": "sha512-YpTYzbUo5HAIF4p5KAgw5oteN+d+TTyVCQzURR0Y+JIhVLI80qQ6Vl/AxUaDRPX5qwX6f+BhNJ3G8tUuMDAdSQ==" + }, "@iconify/json@2.2.382": { "integrity": "sha512-1UT0ouWPVXNteS+kaQjtDvxKy/swWqB84fq9b+xbpE7nhgfak7ljYneWSXTDU+SyfL112F9978p7Mf3C3Q/8LQ==", "dependencies": [ @@ -1130,6 +1134,7 @@ "dependencies": [ "npm:@deno/vite-plugin@^1.0.5", "npm:@fontsource-variable/recursive@^5.2.7", + "npm:@fontsource/flow-block@^5.2.7", "npm:@iconify/json@^2.2.382", "npm:@iconify/tailwind4@^1.0.6", "npm:@sveltejs/adapter-static@^3.0.6", diff --git a/package.json b/package.json index 6c40c0d..5b53a59 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@deno/vite-plugin": "^1.0.5", "@fontsource-variable/recursive": "^5.2.7", + "@fontsource/flow-block": "^5.2.7", "@iconify/json": "^2.2.382", "@iconify/tailwind4": "^1.0.6", "@tailwindcss/forms": "^0.5.10", diff --git a/src-tauri/src/letta/commands.rs b/src-tauri/src/letta/commands.rs index cb6ed27..51f36d3 100644 --- a/src-tauri/src/letta/commands.rs +++ b/src-tauri/src/letta/commands.rs @@ -1,5 +1,5 @@ use crate::{ - letta::{LettaAgentInfo, LettaConfig}, + letta::{LettaAgentInfo, LettaConfigKey}, state::AppState, }; @@ -9,36 +9,47 @@ pub async fn list_agents(state: tauri::State<'_, AppState>) -> Result) -> Result { - let config = state.letta_manager.config.lock().await; +pub async fn get_letta_base_url(state: tauri::State<'_, AppState>) -> Result { + Ok(state.letta_manager.base_url.lock().await.clone()) +} + +#[tauri::command] +pub async fn set_letta_base_url( + state: tauri::State<'_, AppState>, + url: String, +) -> Result { + { + let mut base_url = state.letta_manager.base_url.lock().await; + *base_url = url.clone(); + } + + state + .letta_manager + .store + .set(LettaConfigKey::BaseUrl.to_string(), url.clone()); - Ok(config.clone()) + Ok(url) +} + +#[tauri::command] +pub async fn get_letta_agent_id(state: tauri::State<'_, AppState>) -> Result { + Ok(state.letta_manager.agent_id.lock().await.clone()) } #[tauri::command] -pub async fn update_config( +pub async fn set_letta_agent_id( state: tauri::State<'_, AppState>, - base_url: Option, - agent_id: Option, -) -> Result<(), ()> { + id: String, +) -> Result { { - let mut config = state.letta_manager.config.lock().await; - - *config = LettaConfig { - base_url: if base_url.is_some() { - base_url.unwrap() - } else { - config.base_url.clone() - }, - agent_id: if agent_id.is_some() { - agent_id.unwrap() - } else { - config.agent_id.clone() - }, - }; + let mut agent_id = state.letta_manager.agent_id.lock().await; + *agent_id = id.clone(); } - state.letta_manager.persist_config().await; + state + .letta_manager + .store + .set(LettaConfigKey::AgentId.to_string(), id.clone()); - Ok(()) + Ok(id) } diff --git a/src-tauri/src/letta/mod.rs b/src-tauri/src/letta/mod.rs index 0cbf694..ecbdfc2 100644 --- a/src-tauri/src/letta/mod.rs +++ b/src-tauri/src/letta/mod.rs @@ -2,7 +2,8 @@ use std::sync::Arc; use reqwest::Client; use serde::{Deserialize, Serialize}; -use serde_json::{from_value, json}; +use serde_json::json; +use strum::{Display, EnumString}; use tauri::async_runtime::Mutex; use tauri::Wry; use tauri_plugin_store::Store; @@ -11,11 +12,11 @@ use crate::secrets::SecretsManager; pub mod commands; -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct LettaConfig { - base_url: String, - agent_id: String, +#[derive(Serialize, Deserialize, Clone, Display, EnumString)] +#[strum(prefix = "letta")] +pub enum LettaConfigKey { + BaseUrl, + AgentId, } #[derive(Serialize, Deserialize)] @@ -28,7 +29,8 @@ pub struct LettaManager { http_client: Client, secrets_manager: Arc, store: Arc>, - config: Arc>, + base_url: Mutex, + agent_id: Mutex, } impl LettaManager { pub fn new( @@ -36,30 +38,33 @@ impl LettaManager { store: Arc>, secrets_manager: Arc, ) -> Self { - match store.get("letta-config") { - Some(config) => Self { - http_client, - secrets_manager, - store, - config: Arc::new(Mutex::new( - from_value::(config).expect("failed to deserialize Letta config"), - )), - }, + let base_url = match store.get(LettaConfigKey::BaseUrl.to_string()) { + Some(url) => url + .as_str() + .expect("Letta base URL in config is not a string") + .to_owned(), None => { - let new_config = LettaConfig { - base_url: "https://api.letta.com".to_string(), - agent_id: "".to_string(), - }; + let url = "https://api.letta.com".to_owned(); - store.set("letta-config", json!(new_config)); + store.set(LettaConfigKey::BaseUrl.to_string(), json!(url)); - Self { - http_client, - secrets_manager, - store, - config: Arc::new(Mutex::new(new_config)), - } + url } + }; + let agent_id = match store.get(LettaConfigKey::AgentId.to_string()) { + Some(id) => id + .as_str() + .expect("Letta agent ID in config is not a string") + .to_owned(), + None => "".to_owned(), + }; + + Self { + http_client, + secrets_manager, + store, + base_url: Mutex::new(base_url), + agent_id: Mutex::new(agent_id), } } @@ -69,8 +74,7 @@ impl LettaManager { .get_secret(crate::secrets::SecretName::LettaApiKey) { Ok(api_key) => { - let config = self.config.lock().await; - let base_url = config.base_url.clone(); + let base_url = self.base_url.lock().await.to_owned(); let req = self .http_client .get(format!("{base_url}/v1/agents/")) @@ -87,9 +91,4 @@ impl LettaManager { Err(_) => Vec::new(), } } - - pub async fn persist_config(&self) { - self.store - .set("letta-config", json!(*self.config.lock().await)); - } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 50f2401..536ad8f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -69,8 +69,10 @@ pub fn run() { cartesia::commands::start_stt, cartesia::commands::stop_stt, letta::commands::list_agents, - letta::commands::get_config, - letta::commands::update_config, + letta::commands::get_letta_base_url, + letta::commands::set_letta_base_url, + letta::commands::get_letta_agent_id, + letta::commands::set_letta_agent_id, secrets::commands::has_secret, secrets::commands::set_secret, secrets::commands::delete_secret, diff --git a/src/app.css b/src/app.css index 9d0cb34..6ff4439 100644 --- a/src/app.css +++ b/src/app.css @@ -20,6 +20,7 @@ --color-rose-pine-highlight-high: hsl(248deg, 13%, 36%); --font-sans: "Recursive Variable"; + --font-skeleton: "Flow Block"; --animate-wiggle: wiggle 1s ease-in-out infinite; diff --git a/src/lib/forms/dropdown.svelte b/src/lib/forms/dropdown.svelte index 1ca24c0..8c89986 100644 --- a/src/lib/forms/dropdown.svelte +++ b/src/lib/forms/dropdown.svelte @@ -1,70 +1,88 @@ - - - + + + diff --git a/src/lib/forms/input.svelte b/src/lib/forms/input.svelte index 8059289..e108271 100644 --- a/src/lib/forms/input.svelte +++ b/src/lib/forms/input.svelte @@ -1,10 +1,9 @@ diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b15182e..0b21ad6 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,6 @@ @@ -51,12 +57,12 @@ >

Letta Settings

- {#await lettaKeyGuard then hasKey} + {#await hasApiKey then hasKey} {#if hasKey}
- +

Letta key is set!