some opt + load directly from a store

This commit is contained in:
CJ Pais 2025-05-07 13:14:31 -07:00
parent cbd6a49181
commit d36ddfd61c
13 changed files with 157 additions and 1409 deletions

2
.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[build]
rustc-wrapper = "sccache"

BIN
bun.lockb

Binary file not shown.

View file

@ -22,7 +22,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^4.0.2",
"tauri-plugin-macos-permissions-api": "^2.0.4"
"tauri-plugin-macos-permissions-api": "^2.0.4",
"zod": "^3.24.4"
},
"devDependencies": {
"@types/react": "^18.3.1",

1370
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,9 @@ description = "A Tauri App"
authors = ["you"]
edition = "2021"
[profile.dev]
incremental = true # Compile your binary in smaller steps.
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
@ -29,14 +32,10 @@ cpal = "0.15.3"
whisper-rs = { version = "0.13.2", features = ["whisper-cpp-log"] }
anyhow = "1.0.95"
rubato = "0.16.1"
samplerate = "0.2.4"
rig-core = "0.8.0"
env_logger = "0.11.6"
log = "0.4.25"
tokio = "1.43.0"
vad-rs = "0.1.5"
tauri-plugin-upload = "2"
tauri-plugin-stronghold = "2"
tauri-plugin-store = "2"
[target.'cfg(target_os = "macos")'.dependencies]

View file

@ -6,6 +6,7 @@
"permissions": [
"core:default",
"opener:default",
"store:default",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",

View file

@ -1,25 +1,18 @@
mod managers;
mod shortcut;
use log::info;
use managers::audio::AudioRecordingManager;
use managers::transcription::TranscriptionManager;
use rdev::{simulate, EventType, Key, SimulateError};
use std::sync::Arc;
use std::{thread, time};
use tauri::tray::TrayIconBuilder;
use tauri::{AppHandle, Manager};
use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_global_shortcut::{Code, Modifiers, Shortcut, ShortcutEvent, ShortcutState};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
env_logger::init();
tauri::Builder::default()
.plugin(tauri_plugin_stronghold::Builder::new(|_pass| todo!()).build())
.plugin(tauri_plugin_upload::init())
// .plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
@ -27,6 +20,7 @@ pub fn run() {
))
.plugin(tauri_plugin_macos_permissions::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_store::Builder::default().build())
.setup(move |app| {
let _tray = TrayIconBuilder::new().build(app)?;
@ -57,7 +51,7 @@ pub fn run() {
app.manage(recording_manager.clone());
app.manage(transcription_manager.clone());
shortcut::enable_shortcut(app);
shortcut::init_shortcuts(app);
Ok(())
})

View file

View file

@ -3,6 +3,8 @@ use std::thread;
use std::time;
use rdev::{simulate, EventType, Key, SimulateError};
use serde::Deserialize;
use serde::Serialize;
use tauri::App;
use tauri::AppHandle;
use tauri::Manager;
@ -14,6 +16,41 @@ use tauri_plugin_store::{JsonValue, StoreExt};
use crate::managers::audio::AudioRecordingManager;
use crate::managers::transcription::TranscriptionManager;
#[derive(Serialize, Deserialize, Debug, Clone)] // Clone is useful
pub struct ShortcutBinding {
id: String,
name: String,
description: String,
default_binding: String,
current_binding: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AppSettings {
bindings: Vec<ShortcutBinding>,
}
fn get_default_settings() -> AppSettings {
AppSettings {
bindings: vec![
ShortcutBinding {
id: "transcribe".to_string(),
name: "Transcribe".to_string(),
description: "Converts your speech into text.".to_string(),
default_binding: "alt+space".to_string(),
current_binding: "alt+space".to_string(),
},
ShortcutBinding {
id: "test".to_string(),
name: "Test".to_string(),
description: "This is a test binding.".to_string(),
default_binding: "ctrl+d".to_string(),
current_binding: "ctrl+d".to_string(),
},
],
}
}
fn try_send_event(event: &EventType) {
if let Err(SimulateError) = simulate(event) {
println!("We could not send {:?}", event);
@ -82,7 +119,25 @@ fn transcribe_released(app: &AppHandle) {
});
}
pub fn enable_shortcut(app: &App) {
pub fn init_shortcuts(app: &App) {
// init store
let kb_store = app
.store("settings_store.json")
.expect("Failed to initialize store");
if let Some(bindings) = kb_store.get("settings") {
// print the bindings that exist
println!("Bindings: {:?}", bindings);
} else {
kb_store.set(
"settings",
serde_json::to_value(&get_default_settings()).unwrap(),
);
// create the default bindings
}
// load state from store
_register_shortcut_upon_start(
app,
"alt+space"

0
src-tauri/src/utils.rs Normal file
View file

File diff suppressed because one or more lines are too long

View file

@ -1,44 +1,42 @@
import React from "react";
interface Shortcut {
id: string;
name: string;
description: string;
keys: string[];
}
import React, { useEffect } from "react";
import { load } from "@tauri-apps/plugin-store";
import { SettingsSchema, ShortcutBinding } from "../../lib/types";
export const KeyboardShortcuts: React.FC = () => {
// These would normally come from the backend configuration
const shortcuts: Shortcut[] = [
{
id: "transcribe",
name: "Transcribe",
description: "Convert speech to text",
keys: ["⌃", "Space"],
},
];
const [bindings, setBindings] = React.useState<ShortcutBinding[]>([]);
useEffect(() => {
load("settings_store.json", { autoSave: false }).then((r) => {
console.log("loaded store", r);
r.get("settings").then((s) => {
const settings = SettingsSchema.parse(s);
setBindings(settings.bindings);
});
});
}, []);
return (
<div className="space-y-4">
{shortcuts.map((shortcut) => (
{bindings.map((binding) => (
<div
key={shortcut.id}
key={binding.id}
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:bg-gray-50"
>
<div>
<h3 className="text-sm font-medium text-gray-900">
{shortcut.name}
{binding.name}
</h3>
<p className="text-sm text-gray-500">{shortcut.description}</p>
<p className="text-sm text-gray-500">{binding.description}</p>
</div>
<div className="flex items-center space-x-1">
{shortcut.keys.map((key, index) => (
<React.Fragment key={index}>
<kbd className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded">
{key}
</kbd>
</React.Fragment>
))}
<React.Fragment>
{/* <kbd className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded"> */}
<div className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded">
{binding.current_binding}
</div>
{/* </kbd> */}
</React.Fragment>
</div>
</div>
))}

16
src/lib/types.ts Normal file
View file

@ -0,0 +1,16 @@
import { z } from "zod";
export const ShortcutBindingSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
default_binding: z.string(),
current_binding: z.string(),
});
export const SettingsSchema = z.object({
bindings: z.array(ShortcutBindingSchema),
});
export type ShortcutBinding = z.infer<typeof ShortcutBindingSchema>;
export type Settings = z.infer<typeof SettingsSchema>;