WIP push-to-talk Letta chat frontend

set up window positioning

graham.systems f045a881 38ee9214

verified
+8
.idea/.gitignore
··· 1 + # Default ignored files 2 + /shelf/ 3 + /workspace.xml 4 + # Editor-based HTTP Client requests 5 + /httpRequests/ 6 + # Datasource local storage ignored files 7 + /dataSources/ 8 + /dataSources.local.xml
+11
.idea/miwiwi.iml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <module type="EMPTY_MODULE" version="4"> 3 + <component name="NewModuleRootManager"> 4 + <content url="file://$MODULE_DIR$"> 5 + <sourceFolder url="file://$MODULE_DIR$/src-tauri/src" isTestSource="false" /> 6 + <excludeFolder url="file://$MODULE_DIR$/src-tauri/target" /> 7 + </content> 8 + <orderEntry type="inheritedJdk" /> 9 + <orderEntry type="sourceFolder" forTests="false" /> 10 + </component> 11 + </module>
+8
.idea/modules.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <project version="4"> 3 + <component name="ProjectModuleManager"> 4 + <modules> 5 + <module fileurl="file://$PROJECT_DIR$/.idea/miwiwi.iml" filepath="$PROJECT_DIR$/.idea/miwiwi.iml" /> 6 + </modules> 7 + </component> 8 + </project>
+6
.idea/vcs.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <project version="4"> 3 + <component name="VcsDirectoryMappings"> 4 + <mapping directory="$PROJECT_DIR$" vcs="Jujutsu" /> 5 + </component> 6 + </project>
+2 -1
.vscode/settings.json
··· 1 1 { 2 - "svelte.enable-ts-plugin": true 2 + "svelte.enable-ts-plugin": true, 3 + "deno.enable": true 3 4 }
+5
deno.json
··· 1 + { 2 + "imports": { 3 + "@std/async": "jsr:@std/async@^1.0.14" 4 + } 5 + }
+33
deno.lock
··· 1 1 { 2 2 "version": "5", 3 3 "specifiers": { 4 + "jsr:@std/async@^1.0.14": "1.0.14", 5 + "npm:@deno/vite-plugin@^1.0.5": "1.0.5_vite@6.3.5__picomatch@4.0.3", 4 6 "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", 5 7 "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", 6 8 "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", ··· 8 10 "npm:@tauri-apps/api@2": "2.8.0", 9 11 "npm:@tauri-apps/cli@2": "2.8.2", 10 12 "npm:@tauri-apps/plugin-opener@2": "2.5.0", 13 + "npm:@tauri-apps/plugin-positioner@2.3": "2.3.0", 14 + "npm:@tauri-apps/plugin-window-state@2.4": "2.4.0", 11 15 "npm:svelte-check@4": "4.3.1_svelte@5.38.3__acorn@8.15.0_typescript@5.6.3", 12 16 "npm:svelte@5": "5.38.3_acorn@8.15.0", 13 17 "npm:tailwindcss@^4.1.12": "4.1.12", 14 18 "npm:typescript@~5.6.2": "5.6.3", 15 19 "npm:vite@^6.0.3": "6.3.5_picomatch@4.0.3" 16 20 }, 21 + "jsr": { 22 + "@std/async@1.0.14": { 23 + "integrity": "62e954a418652c704d37563a3e54a37d4cf0268a9dcaeac1660cc652880b5326" 24 + } 25 + }, 17 26 "npm": { 27 + "@deno/vite-plugin@1.0.5_vite@6.3.5__picomatch@4.0.3": { 28 + "integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==", 29 + "dependencies": [ 30 + "vite" 31 + ] 32 + }, 18 33 "@emnapi/core@1.4.5": { 19 34 "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", 20 35 "dependencies": [ ··· 558 573 "@tauri-apps/api" 559 574 ] 560 575 }, 576 + "@tauri-apps/plugin-positioner@2.3.0": { 577 + "integrity": "sha512-YMRcvA+WcULnAS+UjqGLP9ekWAgmBCgvkzT2OTkE8TWcu9jVo/Z79K0nEJhDuhZO/O0b77X0Opd+wDkEDYPeDw==", 578 + "dependencies": [ 579 + "@tauri-apps/api" 580 + ] 581 + }, 582 + "@tauri-apps/plugin-window-state@2.4.0": { 583 + "integrity": "sha512-hRSzPNi2NG0lPFthfVY0V5C1MyWN/gGaQtQYw7i9zZhLzrhZveHZ2omHG1rIiIsjfTGbO7fhjydSoeTTK9GqLw==", 584 + "dependencies": [ 585 + "@tauri-apps/api" 586 + ] 587 + }, 561 588 "@tybys/wasm-util@0.10.0": { 562 589 "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", 563 590 "dependencies": [ ··· 957 984 } 958 985 }, 959 986 "workspace": { 987 + "dependencies": [ 988 + "jsr:@std/async@^1.0.14" 989 + ], 960 990 "packageJson": { 961 991 "dependencies": [ 992 + "npm:@deno/vite-plugin@^1.0.5", 962 993 "npm:@sveltejs/adapter-static@^3.0.6", 963 994 "npm:@sveltejs/kit@^2.9.0", 964 995 "npm:@sveltejs/vite-plugin-svelte@5", ··· 966 997 "npm:@tauri-apps/api@2", 967 998 "npm:@tauri-apps/cli@2", 968 999 "npm:@tauri-apps/plugin-opener@2", 1000 + "npm:@tauri-apps/plugin-positioner@2.3", 1001 + "npm:@tauri-apps/plugin-window-state@2.4", 969 1002 "npm:svelte-check@4", 970 1003 "npm:svelte@5", 971 1004 "npm:tailwindcss@^4.1.12",
+3
package.json
··· 13 13 }, 14 14 "license": "MIT", 15 15 "dependencies": { 16 + "@deno/vite-plugin": "^1.0.5", 16 17 "@tailwindcss/vite": "^4.1.12", 17 18 "@tauri-apps/api": "^2", 18 19 "@tauri-apps/plugin-opener": "^2", 20 + "@tauri-apps/plugin-positioner": "~2.3.0", 21 + "@tauri-apps/plugin-window-state": "~2.4.0", 19 22 "tailwindcss": "^4.1.12" 20 23 }, 21 24 "devDependencies": {
+32
src-tauri/Cargo.lock
··· 2023 2023 "tauri", 2024 2024 "tauri-build", 2025 2025 "tauri-plugin-opener", 2026 + "tauri-plugin-positioner", 2026 2027 "tauri-plugin-single-instance", 2028 + "tauri-plugin-window-state", 2027 2029 ] 2028 2030 2029 2031 [[package]] ··· 3727 3729 ] 3728 3730 3729 3731 [[package]] 3732 + name = "tauri-plugin-positioner" 3733 + version = "2.3.0" 3734 + source = "registry+https://github.com/rust-lang/crates.io-index" 3735 + checksum = "3a01e373ea3f3f5f46d40f434ba13bd12fa4833aabab50dfc09f6362bad27c95" 3736 + dependencies = [ 3737 + "log", 3738 + "serde", 3739 + "serde_json", 3740 + "serde_repr", 3741 + "tauri", 3742 + "tauri-plugin", 3743 + "thiserror 2.0.16", 3744 + ] 3745 + 3746 + [[package]] 3730 3747 name = "tauri-plugin-single-instance" 3731 3748 version = "2.3.3" 3732 3749 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3739 3756 "tracing", 3740 3757 "windows-sys 0.60.2", 3741 3758 "zbus", 3759 + ] 3760 + 3761 + [[package]] 3762 + name = "tauri-plugin-window-state" 3763 + version = "2.4.0" 3764 + source = "registry+https://github.com/rust-lang/crates.io-index" 3765 + checksum = "2d5f6fe3291bfa609c7e0b0ee3bedac294d94c7018934086ce782c1d0f2a468e" 3766 + dependencies = [ 3767 + "bitflags 2.9.3", 3768 + "log", 3769 + "serde", 3770 + "serde_json", 3771 + "tauri", 3772 + "tauri-plugin", 3773 + "thiserror 2.0.16", 3742 3774 ] 3743 3775 3744 3776 [[package]]
+2
src-tauri/Cargo.toml
··· 24 24 serde_json = "1" 25 25 26 26 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 27 + tauri-plugin-positioner = "2" 27 28 tauri-plugin-single-instance = "2" 29 + tauri-plugin-window-state = "2" 28 30
+15
src-tauri/capabilities/desktop.json
··· 1 + { 2 + "identifier": "desktop-capability", 3 + "platforms": [ 4 + "macOS", 5 + "windows", 6 + "linux" 7 + ], 8 + "windows": [ 9 + "main" 10 + ], 11 + "permissions": [ 12 + "positioner:default", 13 + "window-state:default" 14 + ] 15 + }
+60 -28
src-tauri/src/lib.rs
··· 1 - // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ 2 - #[tauri::command] 3 - fn greet(name: &str) -> String { 4 - format!("Hello, {}! You've been greeted from Rust!", name) 5 - } 6 - 7 - #[cfg_attr(mobile, tauri::mobile_entry_point)] 8 - pub fn run() { 9 - let mut builder = tauri::Builder::default(); 10 - 11 - #[cfg(desktop)] 12 - { 13 - builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { 14 - use tauri::Manager; 15 - 16 - let _ = app 17 - .get_webview_window("main") 18 - .expect("no main window") 19 - .set_focus(); 20 - })); 21 - } 22 - 23 - builder 24 - .plugin(tauri_plugin_opener::init()) 25 - .invoke_handler(tauri::generate_handler![greet]) 26 - .run(tauri::generate_context!()) 27 - .expect("error while running tauri application"); 28 - } 1 + use tauri::{WebviewUrl, WebviewWindowBuilder}; 2 + use tauri_plugin_positioner::{Position, WindowExt}; 3 + use tauri_plugin_window_state::{StateFlags, WindowExt as StateWindowExt}; 4 + 5 + // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ 6 + #[tauri::command] 7 + fn greet(name: &str) -> String { 8 + format!("Hello, {}! You've been greeted from Rust!", name) 9 + } 10 + 11 + #[cfg_attr(mobile, tauri::mobile_entry_point)] 12 + pub fn run() { 13 + let mut builder = tauri::Builder::default() 14 + .plugin(tauri_plugin_opener::init()) 15 + .plugin(tauri_plugin_window_state::Builder::new().build()) 16 + .plugin(tauri_plugin_positioner::init()); 17 + 18 + #[cfg(desktop)] 19 + { 20 + builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { 21 + use tauri::Manager; 22 + 23 + let _ = app 24 + .get_webview_window("main") 25 + .expect("no main window") 26 + .set_focus(); 27 + })); 28 + } 29 + 30 + builder 31 + .setup(|app| { 32 + let mut win_builder = 33 + WebviewWindowBuilder::new(app, "main", WebviewUrl::default()).title("miwiwi"); 34 + 35 + #[cfg(desktop)] 36 + { 37 + win_builder = win_builder 38 + .inner_size(600.0, 800.0) 39 + .decorations(false) 40 + .always_on_top(true) 41 + .visible(false) 42 + .maximizable(false); 43 + } 44 + 45 + let window = win_builder.build().expect("failed to build window"); 46 + 47 + window 48 + .move_window(Position::BottomRight) 49 + .expect("could not move window"); 50 + 51 + window 52 + .restore_state(StateFlags::all()) 53 + .expect("could not restore window state"); 54 + 55 + Ok(()) 56 + }) 57 + .invoke_handler(tauri::generate_handler![greet]) 58 + .run(tauri::generate_context!()) 59 + .expect("error while running tauri application"); 60 + }
+1 -7
src-tauri/tauri.conf.json
··· 10 10 "frontendDist": "../build" 11 11 }, 12 12 "app": { 13 - "windows": [ 14 - { 15 - "title": "miwiwi", 16 - "width": 800, 17 - "height": 600 18 - } 19 - ], 13 + "windows": [], 20 14 "security": { 21 15 "csp": null 22 16 }
+9
src/routes/+layout.ts
··· 1 1 import "../app.css"; 2 + import { debounce } from "@std/async"; 3 + import { saveWindowState, StateFlags } from "@tauri-apps/plugin-window-state"; 4 + import { listen, type Event, TauriEvent } from "@tauri-apps/api/event"; 2 5 3 6 // Tauri doesn't have a Node.js server to do proper SSR 4 7 // so we use adapter-static with a fallback to index.html to put the site in SPA mode 5 8 // See: https://svelte.dev/docs/kit/single-page-apps 6 9 // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info 7 10 export const ssr = false; 11 + 12 + const debounced = debounce((_: Event<TauriEvent.WINDOW_RESIZED>) => { 13 + saveWindowState(StateFlags.ALL); 14 + }, 250); 15 + 16 + listen("tauri://resize", debounced);
+3 -2
vite.config.js
··· 1 1 import { defineConfig } from "vite"; 2 2 import { sveltekit } from "@sveltejs/kit/vite"; 3 + import deno from "@deno/vite-plugin"; 3 4 import tailwindcss from "@tailwindcss/vite"; 4 5 5 6 // @ts-expect-error process is a nodejs global 6 - const host = process.env.TAURI_DEV_HOST; 7 + const host = Deno.env.get("TAURI_DEV_HOST"); 7 8 8 9 // https://vite.dev/config/ 9 10 export default defineConfig(async () => ({ 10 - plugins: [sveltekit(), tailwindcss()], 11 + plugins: [sveltekit(), tailwindcss(), deno()], 11 12 12 13 // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 13 14 //