some opt + load directly from a store
This commit is contained in:
parent
cbd6a49181
commit
d36ddfd61c
13 changed files with 157 additions and 1409 deletions
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
rustc-wrapper = "sccache"
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -22,7 +22,8 @@
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"tailwindcss": "^4.0.2",
|
"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": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.1",
|
"@types/react": "^18.3.1",
|
||||||
|
|
|
||||||
1370
src-tauri/Cargo.lock
generated
1370
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,9 @@ description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
@ -29,14 +32,10 @@ cpal = "0.15.3"
|
||||||
whisper-rs = { version = "0.13.2", features = ["whisper-cpp-log"] }
|
whisper-rs = { version = "0.13.2", features = ["whisper-cpp-log"] }
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.95"
|
||||||
rubato = "0.16.1"
|
rubato = "0.16.1"
|
||||||
samplerate = "0.2.4"
|
|
||||||
rig-core = "0.8.0"
|
|
||||||
env_logger = "0.11.6"
|
env_logger = "0.11.6"
|
||||||
log = "0.4.25"
|
log = "0.4.25"
|
||||||
tokio = "1.43.0"
|
tokio = "1.43.0"
|
||||||
vad-rs = "0.1.5"
|
vad-rs = "0.1.5"
|
||||||
tauri-plugin-upload = "2"
|
|
||||||
tauri-plugin-stronghold = "2"
|
|
||||||
tauri-plugin-store = "2"
|
tauri-plugin-store = "2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
|
"store:default",
|
||||||
"global-shortcut:allow-is-registered",
|
"global-shortcut:allow-is-registered",
|
||||||
"global-shortcut:allow-register",
|
"global-shortcut:allow-register",
|
||||||
"global-shortcut:allow-unregister",
|
"global-shortcut:allow-unregister",
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,18 @@
|
||||||
mod managers;
|
mod managers;
|
||||||
mod shortcut;
|
mod shortcut;
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use managers::audio::AudioRecordingManager;
|
use managers::audio::AudioRecordingManager;
|
||||||
use managers::transcription::TranscriptionManager;
|
use managers::transcription::TranscriptionManager;
|
||||||
use rdev::{simulate, EventType, Key, SimulateError};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{thread, time};
|
|
||||||
use tauri::tray::TrayIconBuilder;
|
use tauri::tray::TrayIconBuilder;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::Manager;
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_global_shortcut::{Code, Modifiers, Shortcut, ShortcutEvent, ShortcutState};
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
tauri::Builder::default()
|
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_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_autostart::init(
|
.plugin(tauri_plugin_autostart::init(
|
||||||
MacosLauncher::LaunchAgent,
|
MacosLauncher::LaunchAgent,
|
||||||
|
|
@ -27,6 +20,7 @@ pub fn run() {
|
||||||
))
|
))
|
||||||
.plugin(tauri_plugin_macos_permissions::init())
|
.plugin(tauri_plugin_macos_permissions::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.plugin(tauri_plugin_store::Builder::default().build())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let _tray = TrayIconBuilder::new().build(app)?;
|
let _tray = TrayIconBuilder::new().build(app)?;
|
||||||
|
|
||||||
|
|
@ -57,7 +51,7 @@ pub fn run() {
|
||||||
app.manage(recording_manager.clone());
|
app.manage(recording_manager.clone());
|
||||||
app.manage(transcription_manager.clone());
|
app.manage(transcription_manager.clone());
|
||||||
|
|
||||||
shortcut::enable_shortcut(app);
|
shortcut::init_shortcuts(app);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
|
||||||
0
src-tauri/src/settings.rs
Normal file
0
src-tauri/src/settings.rs
Normal file
|
|
@ -3,6 +3,8 @@ use std::thread;
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
use rdev::{simulate, EventType, Key, SimulateError};
|
use rdev::{simulate, EventType, Key, SimulateError};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
use tauri::App;
|
use tauri::App;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
@ -14,6 +16,41 @@ use tauri_plugin_store::{JsonValue, StoreExt};
|
||||||
use crate::managers::audio::AudioRecordingManager;
|
use crate::managers::audio::AudioRecordingManager;
|
||||||
use crate::managers::transcription::TranscriptionManager;
|
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) {
|
fn try_send_event(event: &EventType) {
|
||||||
if let Err(SimulateError) = simulate(event) {
|
if let Err(SimulateError) = simulate(event) {
|
||||||
println!("We could not send {:?}", 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(
|
_register_shortcut_upon_start(
|
||||||
app,
|
app,
|
||||||
"alt+space"
|
"alt+space"
|
||||||
|
|
|
||||||
0
src-tauri/src/utils.rs
Normal file
0
src-tauri/src/utils.rs
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,44 +1,42 @@
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { load } from "@tauri-apps/plugin-store";
|
||||||
interface Shortcut {
|
import { SettingsSchema, ShortcutBinding } from "../../lib/types";
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
keys: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const KeyboardShortcuts: React.FC = () => {
|
export const KeyboardShortcuts: React.FC = () => {
|
||||||
// These would normally come from the backend configuration
|
const [bindings, setBindings] = React.useState<ShortcutBinding[]>([]);
|
||||||
const shortcuts: Shortcut[] = [
|
|
||||||
{
|
useEffect(() => {
|
||||||
id: "transcribe",
|
load("settings_store.json", { autoSave: false }).then((r) => {
|
||||||
name: "Transcribe",
|
console.log("loaded store", r);
|
||||||
description: "Convert speech to text",
|
|
||||||
keys: ["⌃", "Space"],
|
r.get("settings").then((s) => {
|
||||||
},
|
const settings = SettingsSchema.parse(s);
|
||||||
];
|
setBindings(settings.bindings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{shortcuts.map((shortcut) => (
|
{bindings.map((binding) => (
|
||||||
<div
|
<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"
|
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:bg-gray-50"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900">
|
<h3 className="text-sm font-medium text-gray-900">
|
||||||
{shortcut.name}
|
{binding.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-gray-500">{shortcut.description}</p>
|
<p className="text-sm text-gray-500">{binding.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
{shortcut.keys.map((key, index) => (
|
<React.Fragment>
|
||||||
<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"> */}
|
||||||
<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">
|
||||||
{key}
|
{binding.current_binding}
|
||||||
</kbd>
|
</div>
|
||||||
</React.Fragment>
|
{/* </kbd> */}
|
||||||
))}
|
</React.Fragment>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
16
src/lib/types.ts
Normal file
16
src/lib/types.ts
Normal 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>;
|
||||||
Loading…
Reference in a new issue