WIP push-to-talk Letta chat frontend
0
fork

Configure Feed

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

add letta fields to settings

+431 -120
+13
deno.lock
··· 9 9 "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", 10 10 "npm:@sveltejs/kit@^2.9.0": "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", 11 11 "npm:@sveltejs/vite-plugin-svelte@5": "5.1.1_svelte@5.38.3__acorn@8.15.0_vite@6.3.5__picomatch@4.0.3", 12 + "npm:@tailwindcss/forms@~0.5.10": "0.5.10_tailwindcss@4.1.12", 12 13 "npm:@tailwindcss/vite@^4.1.12": "4.1.12_vite@6.3.5__picomatch@4.0.3", 13 14 "npm:@tauri-apps/api@2": "2.8.0", 14 15 "npm:@tauri-apps/cli@2": "2.8.2", ··· 431 432 "vitefu" 432 433 ] 433 434 }, 435 + "@tailwindcss/forms@0.5.10_tailwindcss@4.1.12": { 436 + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", 437 + "dependencies": [ 438 + "mini-svg-data-uri", 439 + "tailwindcss" 440 + ] 441 + }, 434 442 "@tailwindcss/node@4.1.12": { 435 443 "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", 436 444 "dependencies": [ ··· 877 885 "@jridgewell/sourcemap-codec" 878 886 ] 879 887 }, 888 + "mini-svg-data-uri@1.4.4": { 889 + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", 890 + "bin": true 891 + }, 880 892 "minipass@7.1.2": { 881 893 "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" 882 894 }, ··· 1123 1135 "npm:@sveltejs/adapter-static@^3.0.6", 1124 1136 "npm:@sveltejs/kit@^2.9.0", 1125 1137 "npm:@sveltejs/vite-plugin-svelte@5", 1138 + "npm:@tailwindcss/forms@~0.5.10", 1126 1139 "npm:@tailwindcss/vite@^4.1.12", 1127 1140 "npm:@tauri-apps/api@2", 1128 1141 "npm:@tauri-apps/cli@2",
+1
package.json
··· 17 17 "@fontsource-variable/recursive": "^5.2.7", 18 18 "@iconify/json": "^2.2.382", 19 19 "@iconify/tailwind4": "^1.0.6", 20 + "@tailwindcss/forms": "^0.5.10", 20 21 "@tailwindcss/vite": "^4.1.12", 21 22 "@tauri-apps/api": "^2", 22 23 "@tauri-apps/plugin-http": "~2.5.2",
+1 -1
src-tauri/Cargo.toml
··· 32 32 strum = {version = "0.27.2", features = ["derive"] } 33 33 tauri-plugin-store = "2" 34 34 tauri-plugin-http = "2" 35 - reqwest = { version = "0.12.23", features = ["json"]} 35 + reqwest = { version = "0.12.23", features = ["json"] } 36 36 37 37 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 38 38 tauri-plugin-positioner = "2"
+17 -13
src-tauri/src/letta/commands.rs
··· 21 21 base_url: Option<String>, 22 22 agent_id: Option<String>, 23 23 ) -> Result<(), ()> { 24 - let mut config = state.letta_manager.config.lock().await; 24 + { 25 + let mut config = state.letta_manager.config.lock().await; 25 26 26 - *config = LettaConfig { 27 - base_url: if base_url.is_some() { 28 - base_url.unwrap() 29 - } else { 30 - config.base_url.clone() 31 - }, 32 - agent_id: if agent_id.is_some() { 33 - agent_id.unwrap() 34 - } else { 35 - config.agent_id.clone() 36 - }, 37 - }; 27 + *config = LettaConfig { 28 + base_url: if base_url.is_some() { 29 + base_url.unwrap() 30 + } else { 31 + config.base_url.clone() 32 + }, 33 + agent_id: if agent_id.is_some() { 34 + agent_id.unwrap() 35 + } else { 36 + config.agent_id.clone() 37 + }, 38 + }; 39 + } 40 + 41 + state.letta_manager.persist_config().await; 38 42 39 43 Ok(()) 40 44 }
+26 -16
src-tauri/src/letta/mod.rs
··· 2 2 3 3 use reqwest::Client; 4 4 use serde::{Deserialize, Serialize}; 5 - use serde_json::from_value; 5 + use serde_json::{from_value, json}; 6 6 use tauri::async_runtime::Mutex; 7 7 use tauri::Wry; 8 8 use tauri_plugin_store::Store; ··· 12 12 pub mod commands; 13 13 14 14 #[derive(Serialize, Deserialize, Clone)] 15 - struct LettaConfig { 15 + #[serde(rename_all = "camelCase")] 16 + pub struct LettaConfig { 16 17 base_url: String, 17 18 agent_id: String, 18 19 } ··· 44 45 from_value::<LettaConfig>(config).expect("failed to deserialize Letta config"), 45 46 )), 46 47 }, 47 - None => Self { 48 - http_client, 49 - secrets_manager, 50 - store, 51 - config: Arc::new(Mutex::new(LettaConfig { 48 + None => { 49 + let new_config = LettaConfig { 52 50 base_url: "https://api.letta.com".to_string(), 53 51 agent_id: "".to_string(), 54 - })), 55 - }, 52 + }; 53 + 54 + store.set("letta-config", json!(new_config)); 55 + 56 + Self { 57 + http_client, 58 + secrets_manager, 59 + store, 60 + config: Arc::new(Mutex::new(new_config)), 61 + } 62 + } 56 63 } 57 64 } 58 65 ··· 66 73 let base_url = config.base_url.clone(); 67 74 let req = self 68 75 .http_client 69 - .get(format!("{base_url}/api/v1/agents")) 76 + .get(format!("{base_url}/v1/agents/")) 70 77 .header("Authorization", format!("Bearer {api_key}")); 71 78 72 - let res = req 73 - .send() 74 - .await 75 - .expect("failed to list Letta agents") 79 + let res = req.send().await.expect("failed to list Letta agents"); 80 + let out = res 76 81 .json::<Vec<LettaAgentInfo>>() 77 82 .await 78 - .expect("failed to parse agent list result"); 83 + .expect("failed to deserialize agent info list"); 79 84 80 - res 85 + out 81 86 } 82 87 Err(_) => Vec::new(), 83 88 } 89 + } 90 + 91 + pub async fn persist_config(&self) { 92 + self.store 93 + .set("letta-config", json!(*self.config.lock().await)); 84 94 } 85 95 }
+1
src/app.css
··· 1 1 @import "tailwindcss"; 2 + @plugin "@tailwindcss/forms"; 2 3 @plugin "@iconify/tailwind4"; 3 4 4 5 @theme {
+70
src/lib/forms/dropdown.svelte
··· 1 + <script lang="ts"> 2 + import type { HTMLSelectAttributes } from "svelte/elements"; 3 + 4 + type DropdownProps = HTMLSelectAttributes & { 5 + label: string; 6 + initialOption?: string; 7 + options: DropdownOption[] | Promise<DropdownOption[]> 8 + onSelect: (value: string) => void | Promise<void> 9 + } 10 + 11 + interface DropdownOption { 12 + label: string 13 + value: string 14 + } 15 + 16 + let { 17 + label, 18 + initialOption, 19 + options, 20 + onSelect, 21 + required, 22 + ...props 23 + }: DropdownProps = $props(); 24 + 25 + let value = $state(initialOption) 26 + let isLoading = $derived(!Array.isArray(options)) 27 + 28 + $effect(() => { 29 + if (isLoading && !Array.isArray(options)) { 30 + options.then(() => isLoading = false) 31 + } 32 + }) 33 + </script> 34 + 35 + <label class="flex flex-col gap-0.5 w-full"> 36 + <span class="text-sm text-rose-pine-subtle">{label}</span> 37 + <div 38 + class="flex gap-2" 39 + > 40 + <select 41 + {...props} 42 + bind:value 43 + class={[ 44 + "bg-rose-pine-surface px-3 py-2 w-full flex-1 border-1 border-rose-pine-muted/20 rounded-sm focus:ring-2 ring-rose-pine-iris text-rose-pine-text placeholder:text-rose-pine-subtle", 45 + props.class, 46 + isLoading && "animate-pulse" 47 + ]} 48 + onchange={e => onSelect(e.currentTarget.value)} 49 + > 50 + {#await Promise.resolve(options) then opts} 51 + {#each opts as {value, label}} 52 + <option {value}>{label}</option> 53 + {/each} 54 + {/await} 55 + </select> 56 + {#if !value} 57 + <div class="flex items-center justify-center"> 58 + <span 59 + class="icon-[tabler--exclamation-circle] size-6 text-rose-pine-love" 60 + ></span> 61 + </div> 62 + {:else} 63 + <div class="flex items-center justify-center"> 64 + <span 65 + class="icon-[tabler--circle-check-filled] size-6 text-rose-pine-foam" 66 + ></span> 67 + </div> 68 + {/if} 69 + </div> 70 + </label>
+77
src/lib/forms/input.svelte
··· 1 + <script lang="ts"> 2 + import type { HTMLInputAttributes } from "svelte/elements"; 3 + import { crossfade, fly } from "svelte/transition"; 4 + 5 + type InputProps = HTMLInputAttributes & { 6 + label: string; 7 + initialValue: string; 8 + onSave: (value: string) => void | Promise<void>; 9 + }; 10 + 11 + let { 12 + label, 13 + initialValue, 14 + onSave, 15 + required, 16 + ...props 17 + }: InputProps = $props(); 18 + 19 + let value = $state(initialValue); 20 + let isDirty = $derived(value !== initialValue); 21 + let savePromise: Promise<void> | undefined = $state(); 22 + 23 + function onclick() { 24 + savePromise = Promise.resolve(onSave(value)); 25 + } 26 + 27 + $inspect(value); 28 + </script> 29 + 30 + <label class="flex flex-col gap-0.5"> 31 + <span class="text-sm text-rose-pine-subtle">{label}</span> 32 + <div 33 + class="flex gap-2" 34 + > 35 + <input 36 + {...props} 37 + bind:value 38 + class={[ 39 + "peer block bg-rose-pine-surface px-3 py-2 flex-1 border-1 rounded-sm border-rose-pine-muted/20 focus:ring-2 ring-rose-pine-iris text-rose-pine-text placeholder:text-rose-pine-subtle", 40 + props.class, 41 + ]} 42 + /> 43 + {#if isDirty} 44 + <button 45 + class={[ 46 + "flex items-center justify-center gap-3 py-2 px-4 font-bold rounded-md transition disabled:cursor-not-allowed border-l-2 border-rose-pine-base uppercase text-sm", 47 + "bg-rose-pine-gold text-rose-pine-highlight-low peer-focus:bg-rose-pine-foam focus:bg-rose-pine-foam", 48 + ]} 49 + disabled={!isDirty} 50 + {onclick} 51 + aria-label={`Save changes to ${label}`} 52 + > 53 + {#if savePromise} 54 + {#await savePromise} 55 + loading 56 + {:then _} 57 + saved 58 + {/await} 59 + {:else} 60 + save 61 + {/if} 62 + </button> 63 + {:else if required && !value} 64 + <div class="flex items-center justify-center"> 65 + <span 66 + class="icon-[tabler--exclamation-circle-filled] size-6 text-rose-pine-love" 67 + ></span> 68 + </div> 69 + {:else if required && initialValue} 70 + <div class="flex items-center justify-center"> 71 + <span 72 + class="icon-[tabler--circle-check-filled] size-6 text-rose-pine-foam" 73 + ></span> 74 + </div> 75 + {/if} 76 + </div> 77 + </label>
+12
src/lib/warning.svelte
··· 1 + <script lang="ts"> 2 + let { children } = $props(); 3 + </script> 4 + 5 + <div 6 + class="bg-rose-pine-gold text-rose-pine-highlight-low p-3 rounded-md leading-snug grid grid-cols-[auto_1fr] gap-4" 7 + > 8 + <span class="icon-[tabler--alert-triangle-filled] size-5 self-center"></span> 9 + <div> 10 + {@render children?.()} 11 + </div> 12 + </div>
+13 -17
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import { Channel, invoke } from "@tauri-apps/api/core"; 3 + import Warning from "$lib/warning.svelte"; 3 4 4 5 interface TranscriptionWord { 5 6 word: string; ··· 14 15 invoke("has_secret", { name: "cartesia_api_key" }), 15 16 ); 16 17 17 - $effect(async () => { 18 + $effect(() => { 18 19 if (isRecording) { 19 20 const onEvent = new Channel<TranscriptionWord>(); 20 21 ··· 24 25 25 26 invoke("start_stt", { onEvent }); 26 27 } else { 27 - await invoke("stop_stt"); 28 - 29 - if (draft) { 30 - history.unshift(draft); 31 - draft = ""; 32 - } 28 + invoke("stop_stt").then(() => { 29 + if (draft) { 30 + history.unshift(draft); 31 + draft = ""; 32 + } 33 + }); 33 34 } 34 35 }); 35 36 </script> ··· 76 77 {/if} 77 78 </button> 78 79 {:else} 79 - <div 80 - class="bg-rose-pine-gold rounded-md p-3 text-rose-pine-highlight-low flex gap-3 shadow-xl" 81 - > 82 - <span class="icon-[tabler--exclamation-circle-filled] size-6"></span> 83 - <p> 84 - Set your Cartesia API key in <a href="/settings" class="underline" 85 - >Settings</a> to get started 86 - </p> 87 - </div> 80 + <Warning> 81 + Set your Cartesia API key in <a href="/settings" class="underline" 82 + >Settings</a> to get started 83 + </Warning> 88 84 {/if} 89 85 {:catch err} 90 86 <p>{err}</p> ··· 93 89 94 90 {#if draft} 95 91 <div 96 - class="bg-rose-pine-surface bg-rose-pine-subtle italic rounded-md p-3 flex gap-3 shadow-xl" 92 + class="bg-rose-pine-surface text-rose-pine-subtle italic rounded-md p-3 flex gap-3 shadow-xl" 97 93 > 98 94 {draft} 99 95 </div>
+4 -71
src/routes/settings/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { invoke } from "@tauri-apps/api/core"; 3 - 4 - let keyInvocation = $state( 5 - invoke("has_secret", { name: "cartesia_api_key" }), 6 - ); 7 - let tempCredential = $state(""); 8 - 9 - async function deleteKey() { 10 - await invoke("delete_secret", { name: "cartesia_api_key" }); 11 - keyInvocation = invoke("has_secret", { name: "cartesia_api_key" }); 12 - } 2 + import CartesiaSettings from "./cartesia-settings.svelte"; 3 + import LettaSettings from "./letta-settings.svelte"; 13 4 </script> 14 5 15 6 <div ··· 26 17 </a> 27 18 </div> 28 19 29 - <div class="w-full bg-rose-pine-base p-4 shadow-xl rounded-md"> 30 - <h2 class="text-xl mb-3 font-bold">Cartesia Settings</h2> 31 - 32 - {#await keyInvocation} 33 - <div class="flex items-center justify-center"> 34 - <span 35 - class="icon-[svg-spinners--180-ring-with-bg] text-rose-pine-muted" 36 - ></span> 37 - </div> 38 - {:then hasKey} 39 - {#if hasKey} 40 - <div 41 - class="p-3 rounded-md grid grid-flow-col grid-cols-[auto_auto_1fr] grid-rows-1 bg-rose-pine-pine items-center gap-3" 42 - > 43 - <span class="icon-[tabler--circle-check-filled] size-5"></span> 44 - <p>Cartesia key has been set!</p> 45 - <button 46 - onclick={deleteKey} 47 - class="justify-self-end text-xs hover:underline" 48 - > 49 - delete key 50 - </button> 51 - </div> 52 - {:else} 53 - <label for="cartesia-key" class="block mb-1 text-sm text-rose-pine-subtle" 54 - >API Key</label> 55 - <div class="flex"> 56 - <input 57 - placeholder="my key..." 58 - bind:value={tempCredential} 59 - class="bg-rose-pine-surface px-3 py-2 flex-1" 60 - /> 61 - <button 62 - class="bg-rose-pine-foam px-6 py-2 text-rose-pine-highlight-low font-bold" 63 - onclick={async () => { 64 - await invoke("set_secret", { 65 - name: "cartesia_api_key", 66 - value: tempCredential, 67 - }); 68 - 69 - keyInvocation = invoke("has_secret", { 70 - name: "cartesia_api_key", 71 - }); 72 - }} 73 - > 74 - Save 75 - </button> 76 - </div> 77 - 78 - <p 79 - class="mt-2 bg-rose-pine-gold text-rose-pine-highlight-low p-3 rounded-md leading-snug flex gap-3" 80 - > 81 - <span class="icon-[tabler--exclamation-circle-filled] size-6"></span> 82 - You must set a key before managing other Cartesia settings 83 - </p> 84 - {/if} 85 - {:catch err} 86 - <p>{err}</p> 87 - {/await} 88 - </div> 20 + <LettaSettings /> 21 + <CartesiaSettings />
+65
src/routes/settings/cartesia-settings.svelte
··· 1 + <script lang="ts"> 2 + import Input from "$lib/forms/input.svelte"; 3 + import Warning from "$lib/warning.svelte"; 4 + import { invoke } from "@tauri-apps/api/core"; 5 + 6 + function hasCartesiaKey() { 7 + return invoke("has_secret", { name: "cartesia_api_key" }); 8 + } 9 + 10 + let cartesiaKeyGuard = $state(hasCartesiaKey()); 11 + 12 + async function deleteKey() { 13 + await invoke("delete_secret", { name: "cartesia_api_key" }); 14 + cartesiaKeyGuard = invoke("has_secret", { name: "cartesia_api_key" }); 15 + } 16 + </script> 17 + 18 + <div 19 + class="w-full bg-rose-pine-base p-4 shadow-xl rounded-md flex flex-col gap-4" 20 + > 21 + <h2 class="text-xl font-bold">Cartesia Settings</h2> 22 + 23 + {#await cartesiaKeyGuard} 24 + <div class="flex items-center justify-center"> 25 + <span 26 + class="icon-[svg-spinners--180-ring-with-bg] text-rose-pine-muted" 27 + ></span> 28 + </div> 29 + {:then hasKey} 30 + {#if hasKey} 31 + <div 32 + class="p-3 rounded-md grid grid-flow-col grid-cols-[auto_auto_1fr] grid-rows-1 bg-rose-pine-pine items-center gap-3" 33 + > 34 + <span class="icon-[tabler--circle-check-filled] size-6"></span> 35 + <p>Cartesia key is set!</p> 36 + <button 37 + onclick={deleteKey} 38 + class="justify-self-end text-xs hover:underline" 39 + > 40 + delete key 41 + </button> 42 + </div> 43 + {:else} 44 + <Input 45 + label="API Key" 46 + placeholder="my cartesia key..." 47 + initialValue={""} 48 + required 49 + onSave={async (value) => { 50 + await invoke("set_secret", { 51 + name: "cartesia_api_key", 52 + value, 53 + }); 54 + 55 + cartesiaKeyGuard = hasCartesiaKey(); 56 + }} 57 + /> 58 + <Warning> 59 + You must set a key before managing other Cartesia settings 60 + </Warning> 61 + {/if} 62 + {:catch err} 63 + <p>{err}</p> 64 + {/await} 65 + </div>
+129
src/routes/settings/letta-settings.svelte
··· 1 + <script lang="ts"> 2 + import Dropdown from "$lib/forms/dropdown.svelte"; 3 + import Input from "$lib/forms/input.svelte"; 4 + import Warning from "$lib/warning.svelte"; 5 + import { invoke } from "@tauri-apps/api/core"; 6 + 7 + function hasLettaKey() { 8 + return invoke("has_secret", { name: "letta_api_key" }); 9 + } 10 + 11 + async function deleteKey() { 12 + await invoke("delete_secret", { name: "letta_api_key" }); 13 + 14 + lettaKeyGuard = hasLettaKey(); 15 + } 16 + 17 + interface LettaConfig { 18 + baseUrl: string; 19 + agentId: string; 20 + } 21 + 22 + interface LettaAgentOption { 23 + name: string 24 + id: string 25 + } 26 + 27 + function getLettaConfig(): Promise<LettaConfig> { 28 + return invoke("get_config"); 29 + } 30 + 31 + function getLettaAgents(): Promise<LettaAgentOption[]> { 32 + return invoke("list_agents") 33 + } 34 + 35 + let lettaKeyGuard = $state(hasLettaKey()); 36 + let lettaConfig = $state(getLettaConfig()); 37 + let lettaAgents = $state(getLettaAgents()) 38 + 39 + let showAgentSettings = $derived.by(async () => { 40 + const [hasKey, config] = await Promise.all([ 41 + lettaKeyGuard, 42 + lettaConfig, 43 + ]); 44 + 45 + return Boolean(hasKey && config.baseUrl); 46 + }); 47 + </script> 48 + 49 + <div 50 + class="w-full bg-rose-pine-base p-4 rounded-md flex flex-col gap-4" 51 + > 52 + <h2 class="text-xl font-bold">Letta Settings</h2> 53 + 54 + {#await lettaKeyGuard then hasKey} 55 + {#if hasKey} 56 + <div 57 + class="px-3 py-2 rounded-md grid grid-flow-col grid-cols-[auto_auto_1fr] grid-rows-1 bg-rose-pine-pine items-center gap-3" 58 + > 59 + <span class="icon-[tabler--circle-check-filled] size-5"></span> 60 + <p>Letta key is set!</p> 61 + <button 62 + onclick={deleteKey} 63 + class="justify-self-end text-xs hover:underline" 64 + > 65 + delete key 66 + </button> 67 + </div> 68 + {:else} 69 + <Input 70 + label="API Key" 71 + placeholder="my letta key..." 72 + initialValue={""} 73 + required={true} 74 + onSave={async (key) => { 75 + await invoke("set_secret", { 76 + name: "letta_api_key", 77 + value: key, 78 + }); 79 + 80 + lettaKeyGuard = hasLettaKey(); 81 + }} 82 + /> 83 + {/if} 84 + {:catch err} 85 + <p>{err}</p> 86 + {/await} 87 + 88 + {#await lettaConfig then { baseUrl, agentId }} 89 + <Input 90 + label="Base URL" 91 + placeholder="https://api.letta.com" 92 + initialValue={baseUrl} 93 + required 94 + onSave={async (url) => { 95 + await invoke("update_config", { baseUrl: url }); 96 + lettaConfig = getLettaConfig(); 97 + }} 98 + /> 99 + {#await showAgentSettings then showSettings} 100 + {#if showSettings} 101 + <Dropdown 102 + label="Agent ID" 103 + initialOption={agentId} 104 + options={lettaAgents.then(agents => agents.map(agent => ({ label: agent.name, value: agent.id})))} 105 + onSelect={async id => { 106 + console.log(id) 107 + await invoke("update_config", { agentId: id }) 108 + lettaConfig = getLettaConfig() 109 + }} 110 + /> 111 + {/if} 112 + {/await} 113 + {/await} 114 + 115 + {#await showAgentSettings} 116 + <div class="flex items-center justify-center"> 117 + <span 118 + class="icon-[svg-spinners--180-ring-with-bg] text-rose-pine-muted" 119 + ></span> 120 + </div> 121 + {:then showSettings} 122 + {#if !showSettings} 123 + <Warning> 124 + You must set your API key and base URL before managing other Letta 125 + settings 126 + </Warning> 127 + {/if} 128 + {/await} 129 + </div>
+2 -2
svelte.config.js
··· 2 2 // so we use adapter-static with a fallback to index.html to put the site in SPA mode 3 3 // See: https://svelte.dev/docs/kit/single-page-apps 4 4 // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info 5 - import adapter from "npm:@sveltejs/adapter-static"; 6 - import { vitePreprocess } from "npm:@sveltejs/vite-plugin-svelte"; 5 + import adapter from "@sveltejs/adapter-static"; 6 + import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 7 7 8 8 /** @type {import('@sveltejs/kit').Config} */ 9 9 const config = {