WIP: Add tauri-specta for generating TS bindings (#322)
* initial specta commands * refactor to use the bindings in the ui * merge fixes * remove unused commands * add clamshell to lib * Remove Invoke from everything. * fix settings not being loaded properly * fix settings being overwritten due to deserialization failure + remove provider kind * fix model loading text bug
This commit is contained in:
parent
5085485e67
commit
9521ab9332
42 changed files with 1402 additions and 584 deletions
88
src-tauri/Cargo.lock
generated
88
src-tauri/Cargo.lock
generated
|
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "Inflector"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|
@ -2524,6 +2530,8 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
|
"specta",
|
||||||
|
"specta-typescript",
|
||||||
"strsim",
|
"strsim",
|
||||||
"tar",
|
"tar",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
|
@ -2542,6 +2550,7 @@ dependencies = [
|
||||||
"tauri-plugin-sql",
|
"tauri-plugin-sql",
|
||||||
"tauri-plugin-store",
|
"tauri-plugin-store",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
|
"tauri-specta",
|
||||||
"tokio",
|
"tokio",
|
||||||
"transcribe-rs",
|
"transcribe-rs",
|
||||||
"vad-rs",
|
"vad-rs",
|
||||||
|
|
@ -4306,6 +4315,12 @@ dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pastey"
|
name = "pastey"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -5907,6 +5922,50 @@ dependencies = [
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta"
|
||||||
|
version = "2.0.0-rc.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971"
|
||||||
|
dependencies = [
|
||||||
|
"paste",
|
||||||
|
"specta-macros",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta-macros"
|
||||||
|
version = "2.0.0-rc.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517"
|
||||||
|
dependencies = [
|
||||||
|
"Inflector",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.108",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta-serde"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63"
|
||||||
|
dependencies = [
|
||||||
|
"specta",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta-typescript"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d"
|
||||||
|
dependencies = [
|
||||||
|
"specta",
|
||||||
|
"specta-serde",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
|
@ -6557,6 +6616,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
|
"specta",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-macros",
|
"tauri-macros",
|
||||||
|
|
@ -6952,6 +7012,34 @@ dependencies = [
|
||||||
"wry",
|
"wry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-specta"
|
||||||
|
version = "2.0.0-rc.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b23c0132dd3cf6064e5cd919b82b3f47780e9280e7b5910babfe139829b76655"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"specta",
|
||||||
|
"specta-typescript",
|
||||||
|
"tauri",
|
||||||
|
"tauri-specta-macros",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-specta-macros"
|
||||||
|
version = "2.0.0-rc.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a4aa93823e07859546aa796b8a5d608190cd8037a3a5dce3eb63d491c34bda8"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.108",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,9 @@ tar = "0.4.44"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
transcribe-rs = "0.1.4"
|
transcribe-rs = "0.1.4"
|
||||||
ferrous-opencc = "0.2.3"
|
ferrous-opencc = "0.2.3"
|
||||||
|
specta = "=2.0.0-rc.22"
|
||||||
|
specta-typescript = "0.0.9"
|
||||||
|
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,11 @@ fn send_paste_ctrl_v() -> Result<(), String> {
|
||||||
|
|
||||||
/// Sends a Shift+Insert paste command (Windows and Linux only).
|
/// Sends a Shift+Insert paste command (Windows and Linux only).
|
||||||
/// This is more universal for terminal applications and legacy software.
|
/// This is more universal for terminal applications and legacy software.
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
fn send_paste_shift_insert() -> Result<(), String> {
|
fn send_paste_shift_insert() -> Result<(), String> {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let insert_key_code = Key::Other(0x2D); // VK_INSERT
|
let insert_key_code = Key::Other(0x2D); // VK_INSERT
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let insert_key_code = Key::Other(0x76); // XK_Insert (keycode 118 / 0x76)
|
let insert_key_code = Key::Other(0x76); // XK_Insert (keycode 118 / 0x76, also used as fallback)
|
||||||
|
|
||||||
let mut enigo = Enigo::new(&Settings::default())
|
let mut enigo = Enigo::new(&Settings::default())
|
||||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||||
|
|
@ -109,7 +108,6 @@ fn paste_via_clipboard_ctrl_v(text: &str, app_handle: &AppHandle) -> Result<(),
|
||||||
|
|
||||||
/// Pastes text using the clipboard method with Shift+Insert (Windows/Linux only).
|
/// Pastes text using the clipboard method with Shift+Insert (Windows/Linux only).
|
||||||
/// Saves the current clipboard, writes the text, sends paste command, then restores the clipboard.
|
/// Saves the current clipboard, writes the text, sends paste command, then restores the clipboard.
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
fn paste_via_clipboard_shift_insert(text: &str, app_handle: &AppHandle) -> Result<(), String> {
|
fn paste_via_clipboard_shift_insert(text: &str, app_handle: &AppHandle) -> Result<(), String> {
|
||||||
let clipboard = app_handle.clipboard();
|
let clipboard = app_handle.clipboard();
|
||||||
|
|
||||||
|
|
@ -145,7 +143,6 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
|
||||||
match paste_method {
|
match paste_method {
|
||||||
PasteMethod::CtrlV => paste_via_clipboard_ctrl_v(&text, &app_handle)?,
|
PasteMethod::CtrlV => paste_via_clipboard_ctrl_v(&text, &app_handle)?,
|
||||||
PasteMethod::Direct => paste_via_direct_input(&text)?,
|
PasteMethod::Direct => paste_via_direct_input(&text)?,
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
PasteMethod::ShiftInsert => paste_via_clipboard_shift_insert(&text, &app_handle)?,
|
PasteMethod::ShiftInsert => paste_via_clipboard_shift_insert(&text, &app_handle)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ use crate::managers::audio::{AudioRecordingManager, MicrophoneMode};
|
||||||
use crate::settings::{get_settings, write_settings};
|
use crate::settings::{get_settings, write_settings};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Type)]
|
||||||
pub struct CustomSounds {
|
pub struct CustomSounds {
|
||||||
start: bool,
|
start: bool,
|
||||||
stop: bool,
|
stop: bool,
|
||||||
|
|
@ -23,6 +24,7 @@ fn custom_sound_exists(app: &AppHandle, sound_type: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn check_custom_sounds(app: AppHandle) -> CustomSounds {
|
pub fn check_custom_sounds(app: AppHandle) -> CustomSounds {
|
||||||
CustomSounds {
|
CustomSounds {
|
||||||
start: custom_sound_exists(&app, "start"),
|
start: custom_sound_exists(&app, "start"),
|
||||||
|
|
@ -30,7 +32,7 @@ pub fn check_custom_sounds(app: AppHandle) -> CustomSounds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
||||||
pub struct AudioDevice {
|
pub struct AudioDevice {
|
||||||
pub index: String,
|
pub index: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -38,6 +40,7 @@ pub struct AudioDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn update_microphone_mode(app: AppHandle, always_on: bool) -> Result<(), String> {
|
pub fn update_microphone_mode(app: AppHandle, always_on: bool) -> Result<(), String> {
|
||||||
// Update settings
|
// Update settings
|
||||||
let mut settings = get_settings(&app);
|
let mut settings = get_settings(&app);
|
||||||
|
|
@ -57,12 +60,14 @@ pub fn update_microphone_mode(app: AppHandle, always_on: bool) -> Result<(), Str
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_microphone_mode(app: AppHandle) -> Result<bool, String> {
|
pub fn get_microphone_mode(app: AppHandle) -> Result<bool, String> {
|
||||||
let settings = get_settings(&app);
|
let settings = get_settings(&app);
|
||||||
Ok(settings.always_on_microphone)
|
Ok(settings.always_on_microphone)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_available_microphones() -> Result<Vec<AudioDevice>, String> {
|
pub fn get_available_microphones() -> Result<Vec<AudioDevice>, String> {
|
||||||
let devices =
|
let devices =
|
||||||
list_input_devices().map_err(|e| format!("Failed to list audio devices: {}", e))?;
|
list_input_devices().map_err(|e| format!("Failed to list audio devices: {}", e))?;
|
||||||
|
|
@ -83,6 +88,7 @@ pub fn get_available_microphones() -> Result<Vec<AudioDevice>, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_selected_microphone(app: AppHandle, device_name: String) -> Result<(), String> {
|
pub fn set_selected_microphone(app: AppHandle, device_name: String) -> Result<(), String> {
|
||||||
let mut settings = get_settings(&app);
|
let mut settings = get_settings(&app);
|
||||||
settings.selected_microphone = if device_name == "default" {
|
settings.selected_microphone = if device_name == "default" {
|
||||||
|
|
@ -101,6 +107,7 @@ pub fn set_selected_microphone(app: AppHandle, device_name: String) -> Result<()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_selected_microphone(app: AppHandle) -> Result<String, String> {
|
pub fn get_selected_microphone(app: AppHandle) -> Result<String, String> {
|
||||||
let settings = get_settings(&app);
|
let settings = get_settings(&app);
|
||||||
Ok(settings
|
Ok(settings
|
||||||
|
|
@ -109,6 +116,7 @@ pub fn get_selected_microphone(app: AppHandle) -> Result<String, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_available_output_devices() -> Result<Vec<AudioDevice>, String> {
|
pub fn get_available_output_devices() -> Result<Vec<AudioDevice>, String> {
|
||||||
let devices =
|
let devices =
|
||||||
list_output_devices().map_err(|e| format!("Failed to list output devices: {}", e))?;
|
list_output_devices().map_err(|e| format!("Failed to list output devices: {}", e))?;
|
||||||
|
|
@ -129,6 +137,7 @@ pub fn get_available_output_devices() -> Result<Vec<AudioDevice>, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_selected_output_device(app: AppHandle, device_name: String) -> Result<(), String> {
|
pub fn set_selected_output_device(app: AppHandle, device_name: String) -> Result<(), String> {
|
||||||
let mut settings = get_settings(&app);
|
let mut settings = get_settings(&app);
|
||||||
settings.selected_output_device = if device_name == "default" {
|
settings.selected_output_device = if device_name == "default" {
|
||||||
|
|
@ -141,6 +150,7 @@ pub fn set_selected_output_device(app: AppHandle, device_name: String) -> Result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_selected_output_device(app: AppHandle) -> Result<String, String> {
|
pub fn get_selected_output_device(app: AppHandle) -> Result<String, String> {
|
||||||
let settings = get_settings(&app);
|
let settings = get_settings(&app);
|
||||||
Ok(settings
|
Ok(settings
|
||||||
|
|
@ -149,6 +159,7 @@ pub fn get_selected_output_device(app: AppHandle) -> Result<String, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn play_test_sound(app: AppHandle, sound_type: String) {
|
pub fn play_test_sound(app: AppHandle, sound_type: String) {
|
||||||
let sound = match sound_type.as_str() {
|
let sound = match sound_type.as_str() {
|
||||||
"start" => audio_feedback::SoundType::Start,
|
"start" => audio_feedback::SoundType::Start,
|
||||||
|
|
@ -162,6 +173,7 @@ pub fn play_test_sound(app: AppHandle, sound_type: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_clamshell_microphone(app: AppHandle, device_name: String) -> Result<(), String> {
|
pub fn set_clamshell_microphone(app: AppHandle, device_name: String) -> Result<(), String> {
|
||||||
let mut settings = get_settings(&app);
|
let mut settings = get_settings(&app);
|
||||||
settings.clamshell_microphone = if device_name == "default" {
|
settings.clamshell_microphone = if device_name == "default" {
|
||||||
|
|
@ -174,6 +186,7 @@ pub fn set_clamshell_microphone(app: AppHandle, device_name: String) -> Result<(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_clamshell_microphone(app: AppHandle) -> Result<String, String> {
|
pub fn get_clamshell_microphone(app: AppHandle) -> Result<String, String> {
|
||||||
let settings = get_settings(&app);
|
let settings = get_settings(&app);
|
||||||
Ok(settings
|
Ok(settings
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||||
use tauri::{AppHandle, State};
|
use tauri::{AppHandle, State};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_history_entries(
|
pub async fn get_history_entries(
|
||||||
_app: AppHandle,
|
_app: AppHandle,
|
||||||
history_manager: State<'_, Arc<HistoryManager>>,
|
history_manager: State<'_, Arc<HistoryManager>>,
|
||||||
|
|
@ -14,6 +15,7 @@ pub async fn get_history_entries(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn toggle_history_entry_saved(
|
pub async fn toggle_history_entry_saved(
|
||||||
_app: AppHandle,
|
_app: AppHandle,
|
||||||
history_manager: State<'_, Arc<HistoryManager>>,
|
history_manager: State<'_, Arc<HistoryManager>>,
|
||||||
|
|
@ -26,6 +28,7 @@ pub async fn toggle_history_entry_saved(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_audio_file_path(
|
pub async fn get_audio_file_path(
|
||||||
_app: AppHandle,
|
_app: AppHandle,
|
||||||
history_manager: State<'_, Arc<HistoryManager>>,
|
history_manager: State<'_, Arc<HistoryManager>>,
|
||||||
|
|
@ -38,6 +41,7 @@ pub async fn get_audio_file_path(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn delete_history_entry(
|
pub async fn delete_history_entry(
|
||||||
_app: AppHandle,
|
_app: AppHandle,
|
||||||
history_manager: State<'_, Arc<HistoryManager>>,
|
history_manager: State<'_, Arc<HistoryManager>>,
|
||||||
|
|
@ -50,6 +54,7 @@ pub async fn delete_history_entry(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn update_history_limit(
|
pub async fn update_history_limit(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
history_manager: State<'_, Arc<HistoryManager>>,
|
history_manager: State<'_, Arc<HistoryManager>>,
|
||||||
|
|
@ -67,6 +72,7 @@ pub async fn update_history_limit(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn update_recording_retention_period(
|
pub async fn update_recording_retention_period(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
history_manager: State<'_, Arc<HistoryManager>>,
|
history_manager: State<'_, Arc<HistoryManager>>,
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,19 @@ pub mod history;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod transcription;
|
pub mod transcription;
|
||||||
|
|
||||||
use crate::{settings, utils::cancel_current_operation};
|
use crate::settings::{get_settings, write_settings, AppSettings, LogLevel};
|
||||||
|
use crate::utils::cancel_current_operation;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
use tauri_plugin_log::LogLevel;
|
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn cancel_operation(app: AppHandle) {
|
pub fn cancel_operation(app: AppHandle) {
|
||||||
cancel_current_operation(&app);
|
cancel_current_operation(&app);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_app_dir_path(app: AppHandle) -> Result<String, String> {
|
pub fn get_app_dir_path(app: AppHandle) -> Result<String, String> {
|
||||||
let app_data_dir = app
|
let app_data_dir = app
|
||||||
.path()
|
.path()
|
||||||
|
|
@ -24,6 +26,19 @@ pub fn get_app_dir_path(app: AppHandle) -> Result<String, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub fn get_app_settings(app: AppHandle) -> Result<AppSettings, String> {
|
||||||
|
Ok(get_settings(&app))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub fn get_default_settings() -> Result<AppSettings, String> {
|
||||||
|
Ok(crate::settings::get_default_settings())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_log_dir_path(app: AppHandle) -> Result<String, String> {
|
pub fn get_log_dir_path(app: AppHandle) -> Result<String, String> {
|
||||||
let log_dir = app
|
let log_dir = app
|
||||||
.path()
|
.path()
|
||||||
|
|
@ -33,22 +48,25 @@ pub fn get_log_dir_path(app: AppHandle) -> Result<String, String> {
|
||||||
Ok(log_dir.to_string_lossy().to_string())
|
Ok(log_dir.to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[specta::specta]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn set_log_level(app: AppHandle, level: LogLevel) -> Result<(), String> {
|
pub fn set_log_level(app: AppHandle, level: LogLevel) -> Result<(), String> {
|
||||||
let log_level: log::Level = level.clone().into();
|
let tauri_log_level: tauri_plugin_log::LogLevel = level.into();
|
||||||
|
let log_level: log::Level = tauri_log_level.into();
|
||||||
// Update the file log level atomic so the filter picks up the new level
|
// Update the file log level atomic so the filter picks up the new level
|
||||||
crate::FILE_LOG_LEVEL.store(
|
crate::FILE_LOG_LEVEL.store(
|
||||||
log_level.to_level_filter() as u8,
|
log_level.to_level_filter() as u8,
|
||||||
std::sync::atomic::Ordering::Relaxed,
|
std::sync::atomic::Ordering::Relaxed,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = get_settings(&app);
|
||||||
settings.log_level = level;
|
settings.log_level = level;
|
||||||
settings::write_settings(&app, settings);
|
write_settings(&app, settings);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[specta::specta]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_recordings_folder(app: AppHandle) -> Result<(), String> {
|
pub fn open_recordings_folder(app: AppHandle) -> Result<(), String> {
|
||||||
let app_data_dir = app
|
let app_data_dir = app
|
||||||
|
|
@ -66,6 +84,7 @@ pub fn open_recordings_folder(app: AppHandle) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[specta::specta]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_log_dir(app: AppHandle) -> Result<(), String> {
|
pub fn open_log_dir(app: AppHandle) -> Result<(), String> {
|
||||||
let log_dir = app
|
let log_dir = app
|
||||||
|
|
@ -81,6 +100,7 @@ pub fn open_log_dir(app: AppHandle) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[specta::specta]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_app_data_dir(app: AppHandle) -> Result<(), String> {
|
pub fn open_app_data_dir(app: AppHandle) -> Result<(), String> {
|
||||||
let app_data_dir = app
|
let app_data_dir = app
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||||
use tauri::{AppHandle, State};
|
use tauri::{AppHandle, State};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_available_models(
|
pub async fn get_available_models(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
) -> Result<Vec<ModelInfo>, String> {
|
) -> Result<Vec<ModelInfo>, String> {
|
||||||
|
|
@ -12,6 +13,7 @@ pub async fn get_available_models(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_model_info(
|
pub async fn get_model_info(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
model_id: String,
|
model_id: String,
|
||||||
|
|
@ -20,6 +22,7 @@ pub async fn get_model_info(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn download_model(
|
pub async fn download_model(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
model_id: String,
|
model_id: String,
|
||||||
|
|
@ -31,6 +34,7 @@ pub async fn download_model(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn delete_model(
|
pub async fn delete_model(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
model_id: String,
|
model_id: String,
|
||||||
|
|
@ -41,6 +45,7 @@ pub async fn delete_model(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn set_active_model(
|
pub async fn set_active_model(
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
|
|
@ -70,12 +75,14 @@ pub async fn set_active_model(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_current_model(app_handle: AppHandle) -> Result<String, String> {
|
pub async fn get_current_model(app_handle: AppHandle) -> Result<String, String> {
|
||||||
let settings = get_settings(&app_handle);
|
let settings = get_settings(&app_handle);
|
||||||
Ok(settings.selected_model)
|
Ok(settings.selected_model)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_transcription_model_status(
|
pub async fn get_transcription_model_status(
|
||||||
transcription_manager: State<'_, Arc<TranscriptionManager>>,
|
transcription_manager: State<'_, Arc<TranscriptionManager>>,
|
||||||
) -> Result<Option<String>, String> {
|
) -> Result<Option<String>, String> {
|
||||||
|
|
@ -83,6 +90,7 @@ pub async fn get_transcription_model_status(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn is_model_loading(
|
pub async fn is_model_loading(
|
||||||
transcription_manager: State<'_, Arc<TranscriptionManager>>,
|
transcription_manager: State<'_, Arc<TranscriptionManager>>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
|
|
@ -92,6 +100,7 @@ pub async fn is_model_loading(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn has_any_models_available(
|
pub async fn has_any_models_available(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
|
|
@ -100,6 +109,7 @@ pub async fn has_any_models_available(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn has_any_models_or_downloads(
|
pub async fn has_any_models_or_downloads(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
|
|
@ -109,6 +119,7 @@ pub async fn has_any_models_or_downloads(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn cancel_download(
|
pub async fn cancel_download(
|
||||||
model_manager: State<'_, Arc<ModelManager>>,
|
model_manager: State<'_, Arc<ModelManager>>,
|
||||||
model_id: String,
|
model_id: String,
|
||||||
|
|
@ -119,6 +130,7 @@ pub async fn cancel_download(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_recommended_first_model() -> Result<String, String> {
|
pub async fn get_recommended_first_model() -> Result<String, String> {
|
||||||
// Recommend Parakeet V3 model for first-time users - fastest and most accurate
|
// Recommend Parakeet V3 model for first-time users - fastest and most accurate
|
||||||
Ok("parakeet-tdt-0.6b-v3".to_string())
|
Ok("parakeet-tdt-0.6b-v3".to_string())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
use crate::managers::transcription::TranscriptionManager;
|
use crate::managers::transcription::TranscriptionManager;
|
||||||
use crate::settings::{get_settings, write_settings, ModelUnloadTimeout};
|
use crate::settings::{get_settings, write_settings, ModelUnloadTimeout};
|
||||||
|
use serde::Serialize;
|
||||||
|
use specta::Type;
|
||||||
use tauri::{AppHandle, State};
|
use tauri::{AppHandle, State};
|
||||||
|
|
||||||
|
#[derive(Serialize, Type)]
|
||||||
|
pub struct ModelLoadStatus {
|
||||||
|
is_loaded: bool,
|
||||||
|
current_model: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_model_unload_timeout(app: AppHandle, timeout: ModelUnloadTimeout) {
|
pub fn set_model_unload_timeout(app: AppHandle, timeout: ModelUnloadTimeout) {
|
||||||
let mut settings = get_settings(&app);
|
let mut settings = get_settings(&app);
|
||||||
settings.model_unload_timeout = timeout;
|
settings.model_unload_timeout = timeout;
|
||||||
|
|
@ -10,19 +19,18 @@ pub fn set_model_unload_timeout(app: AppHandle, timeout: ModelUnloadTimeout) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_model_load_status(
|
pub fn get_model_load_status(
|
||||||
transcription_manager: State<TranscriptionManager>,
|
transcription_manager: State<TranscriptionManager>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<ModelLoadStatus, String> {
|
||||||
let is_loaded = transcription_manager.is_model_loaded();
|
Ok(ModelLoadStatus {
|
||||||
let current_model = transcription_manager.get_current_model();
|
is_loaded: transcription_manager.is_model_loaded(),
|
||||||
|
current_model: transcription_manager.get_current_model(),
|
||||||
Ok(serde_json::json!({
|
})
|
||||||
"is_loaded": is_loaded,
|
|
||||||
"current_model": current_model
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn unload_model_manually(
|
pub fn unload_model_manually(
|
||||||
transcription_manager: State<TranscriptionManager>,
|
transcription_manager: State<TranscriptionManager>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use std::process::Command;
|
||||||
/// This queries the macOS IORegistry for the AppleClamshellState key.
|
/// This queries the macOS IORegistry for the AppleClamshellState key.
|
||||||
/// Returns true if the lid is closed, false if open.
|
/// Returns true if the lid is closed, false if open.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
#[tauri::command]
|
|
||||||
pub fn is_clamshell() -> Result<bool, String> {
|
pub fn is_clamshell() -> Result<bool, String> {
|
||||||
let output = Command::new("ioreg")
|
let output = Command::new("ioreg")
|
||||||
.args(["-r", "-k", "AppleClamshellState", "-d", "4"])
|
.args(["-r", "-k", "AppleClamshellState", "-d", "4"])
|
||||||
|
|
@ -32,6 +31,7 @@ pub fn is_clamshell() -> Result<bool, String> {
|
||||||
/// Returns true if a battery is detected (laptop), false otherwise (desktop)
|
/// Returns true if a battery is detected (laptop), false otherwise (desktop)
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn is_laptop() -> Result<bool, String> {
|
pub fn is_laptop() -> Result<bool, String> {
|
||||||
let output = Command::new("pmset")
|
let output = Command::new("pmset")
|
||||||
.arg("-g")
|
.arg("-g")
|
||||||
|
|
@ -48,7 +48,6 @@ pub fn is_laptop() -> Result<bool, String> {
|
||||||
/// Stub implementation for non-macOS platforms
|
/// Stub implementation for non-macOS platforms
|
||||||
/// Always returns false since clamshell mode is macOS-specific
|
/// Always returns false since clamshell mode is macOS-specific
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
#[tauri::command]
|
|
||||||
pub fn is_clamshell() -> Result<bool, String> {
|
pub fn is_clamshell() -> Result<bool, String> {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +56,7 @@ pub fn is_clamshell() -> Result<bool, String> {
|
||||||
/// Always returns false since laptop detection is macOS-specific
|
/// Always returns false since laptop detection is macOS-specific
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn is_laptop() -> Result<bool, String> {
|
pub fn is_laptop() -> Result<bool, String> {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ mod shortcut;
|
||||||
mod signal_handle;
|
mod signal_handle;
|
||||||
mod tray;
|
mod tray;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
use specta_typescript::{BigIntExportBehavior, Typescript};
|
||||||
|
use tauri_specta::{collect_commands, Builder};
|
||||||
|
|
||||||
use env_filter::Builder as EnvFilterBuilder;
|
use env_filter::Builder as EnvFilterBuilder;
|
||||||
use managers::audio::AudioRecordingManager;
|
use managers::audio::AudioRecordingManager;
|
||||||
|
|
@ -33,6 +35,8 @@ use tauri::{AppHandle, Manager};
|
||||||
use tauri_plugin_autostart::{MacosLauncher, ManagerExt};
|
use tauri_plugin_autostart::{MacosLauncher, ManagerExt};
|
||||||
use tauri_plugin_log::{Builder as LogBuilder, RotationStrategy, Target, TargetKind};
|
use tauri_plugin_log::{Builder as LogBuilder, RotationStrategy, Target, TargetKind};
|
||||||
|
|
||||||
|
use crate::settings::get_settings;
|
||||||
|
|
||||||
// Global atomic to store the file log level filter
|
// Global atomic to store the file log level filter
|
||||||
// We use u8 to store the log::LevelFilter as a number
|
// We use u8 to store the log::LevelFilter as a number
|
||||||
pub static FILE_LOG_LEVEL: AtomicU8 = AtomicU8::new(log::LevelFilter::Debug as u8);
|
pub static FILE_LOG_LEVEL: AtomicU8 = AtomicU8::new(log::LevelFilter::Debug as u8);
|
||||||
|
|
@ -199,6 +203,7 @@ fn initialize_core_logic(app_handle: &AppHandle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
||||||
app.emit("check-for-updates", ())
|
app.emit("check-for-updates", ())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
@ -211,6 +216,90 @@ pub fn run() {
|
||||||
// when the variable is unset
|
// when the variable is unset
|
||||||
let console_filter = build_console_filter();
|
let console_filter = build_console_filter();
|
||||||
|
|
||||||
|
let specta_builder = Builder::<tauri::Wry>::new().commands(collect_commands![
|
||||||
|
shortcut::change_binding,
|
||||||
|
shortcut::reset_binding,
|
||||||
|
shortcut::change_ptt_setting,
|
||||||
|
shortcut::change_audio_feedback_setting,
|
||||||
|
shortcut::change_audio_feedback_volume_setting,
|
||||||
|
shortcut::change_sound_theme_setting,
|
||||||
|
shortcut::change_start_hidden_setting,
|
||||||
|
shortcut::change_autostart_setting,
|
||||||
|
shortcut::change_translate_to_english_setting,
|
||||||
|
shortcut::change_selected_language_setting,
|
||||||
|
shortcut::change_overlay_position_setting,
|
||||||
|
shortcut::change_debug_mode_setting,
|
||||||
|
shortcut::change_word_correction_threshold_setting,
|
||||||
|
shortcut::change_paste_method_setting,
|
||||||
|
shortcut::change_clipboard_handling_setting,
|
||||||
|
shortcut::change_post_process_enabled_setting,
|
||||||
|
shortcut::change_post_process_base_url_setting,
|
||||||
|
shortcut::change_post_process_api_key_setting,
|
||||||
|
shortcut::change_post_process_model_setting,
|
||||||
|
shortcut::set_post_process_provider,
|
||||||
|
shortcut::fetch_post_process_models,
|
||||||
|
shortcut::add_post_process_prompt,
|
||||||
|
shortcut::update_post_process_prompt,
|
||||||
|
shortcut::delete_post_process_prompt,
|
||||||
|
shortcut::set_post_process_selected_prompt,
|
||||||
|
shortcut::update_custom_words,
|
||||||
|
shortcut::suspend_binding,
|
||||||
|
shortcut::resume_binding,
|
||||||
|
shortcut::change_mute_while_recording_setting,
|
||||||
|
trigger_update_check,
|
||||||
|
commands::cancel_operation,
|
||||||
|
commands::get_app_dir_path,
|
||||||
|
commands::get_app_settings,
|
||||||
|
commands::get_default_settings,
|
||||||
|
commands::get_log_dir_path,
|
||||||
|
commands::set_log_level,
|
||||||
|
commands::open_recordings_folder,
|
||||||
|
commands::open_log_dir,
|
||||||
|
commands::open_app_data_dir,
|
||||||
|
commands::models::get_available_models,
|
||||||
|
commands::models::get_model_info,
|
||||||
|
commands::models::download_model,
|
||||||
|
commands::models::delete_model,
|
||||||
|
commands::models::cancel_download,
|
||||||
|
commands::models::set_active_model,
|
||||||
|
commands::models::get_current_model,
|
||||||
|
commands::models::get_transcription_model_status,
|
||||||
|
commands::models::is_model_loading,
|
||||||
|
commands::models::has_any_models_available,
|
||||||
|
commands::models::has_any_models_or_downloads,
|
||||||
|
commands::models::get_recommended_first_model,
|
||||||
|
commands::audio::update_microphone_mode,
|
||||||
|
commands::audio::get_microphone_mode,
|
||||||
|
commands::audio::get_available_microphones,
|
||||||
|
commands::audio::set_selected_microphone,
|
||||||
|
commands::audio::get_selected_microphone,
|
||||||
|
commands::audio::get_available_output_devices,
|
||||||
|
commands::audio::set_selected_output_device,
|
||||||
|
commands::audio::get_selected_output_device,
|
||||||
|
commands::audio::play_test_sound,
|
||||||
|
commands::audio::check_custom_sounds,
|
||||||
|
commands::audio::set_clamshell_microphone,
|
||||||
|
commands::audio::get_clamshell_microphone,
|
||||||
|
commands::transcription::set_model_unload_timeout,
|
||||||
|
commands::transcription::get_model_load_status,
|
||||||
|
commands::transcription::unload_model_manually,
|
||||||
|
commands::history::get_history_entries,
|
||||||
|
commands::history::toggle_history_entry_saved,
|
||||||
|
commands::history::get_audio_file_path,
|
||||||
|
commands::history::delete_history_entry,
|
||||||
|
commands::history::update_history_limit,
|
||||||
|
commands::history::update_recording_retention_period,
|
||||||
|
helpers::clamshell::is_laptop,
|
||||||
|
]);
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)] // <- Only export on non-release builds
|
||||||
|
specta_builder
|
||||||
|
.export(
|
||||||
|
Typescript::default().bigint(BigIntExportBehavior::String),
|
||||||
|
"../src/bindings.ts",
|
||||||
|
)
|
||||||
|
.expect("Failed to export typescript bindings");
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
.plugin(
|
.plugin(
|
||||||
LogBuilder::new()
|
LogBuilder::new()
|
||||||
|
|
@ -268,8 +357,9 @@ pub fn run() {
|
||||||
))
|
))
|
||||||
.manage(Mutex::new(ShortcutToggleStates::default()))
|
.manage(Mutex::new(ShortcutToggleStates::default()))
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let settings = settings::get_settings(&app.handle());
|
let settings = get_settings(&app.handle());
|
||||||
let file_log_level: log::Level = settings.log_level.clone().into();
|
let tauri_log_level: tauri_plugin_log::LogLevel = settings.log_level.into();
|
||||||
|
let file_log_level: log::Level = tauri_log_level.into();
|
||||||
// Store the file log level in the atomic for the filter to use
|
// Store the file log level in the atomic for the filter to use
|
||||||
FILE_LOG_LEVEL.store(file_log_level.to_level_filter() as u8, Ordering::Relaxed);
|
FILE_LOG_LEVEL.store(file_log_level.to_level_filter() as u8, Ordering::Relaxed);
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
|
|
@ -307,80 +397,7 @@ pub fn run() {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(specta_builder.invoke_handler())
|
||||||
shortcut::change_binding,
|
|
||||||
shortcut::reset_binding,
|
|
||||||
shortcut::change_ptt_setting,
|
|
||||||
shortcut::change_audio_feedback_setting,
|
|
||||||
shortcut::change_audio_feedback_volume_setting,
|
|
||||||
shortcut::change_sound_theme_setting,
|
|
||||||
shortcut::change_start_hidden_setting,
|
|
||||||
shortcut::change_autostart_setting,
|
|
||||||
shortcut::change_translate_to_english_setting,
|
|
||||||
shortcut::change_selected_language_setting,
|
|
||||||
shortcut::change_overlay_position_setting,
|
|
||||||
shortcut::change_debug_mode_setting,
|
|
||||||
shortcut::change_word_correction_threshold_setting,
|
|
||||||
shortcut::change_paste_method_setting,
|
|
||||||
shortcut::change_clipboard_handling_setting,
|
|
||||||
shortcut::change_post_process_enabled_setting,
|
|
||||||
shortcut::change_post_process_base_url_setting,
|
|
||||||
shortcut::change_post_process_api_key_setting,
|
|
||||||
shortcut::change_post_process_model_setting,
|
|
||||||
shortcut::set_post_process_provider,
|
|
||||||
shortcut::fetch_post_process_models,
|
|
||||||
shortcut::add_post_process_prompt,
|
|
||||||
shortcut::update_post_process_prompt,
|
|
||||||
shortcut::delete_post_process_prompt,
|
|
||||||
shortcut::set_post_process_selected_prompt,
|
|
||||||
shortcut::update_custom_words,
|
|
||||||
shortcut::suspend_binding,
|
|
||||||
shortcut::resume_binding,
|
|
||||||
shortcut::change_mute_while_recording_setting,
|
|
||||||
trigger_update_check,
|
|
||||||
commands::cancel_operation,
|
|
||||||
commands::get_app_dir_path,
|
|
||||||
commands::get_log_dir_path,
|
|
||||||
commands::set_log_level,
|
|
||||||
commands::open_recordings_folder,
|
|
||||||
commands::open_log_dir,
|
|
||||||
commands::open_app_data_dir,
|
|
||||||
commands::models::get_available_models,
|
|
||||||
commands::models::get_model_info,
|
|
||||||
commands::models::download_model,
|
|
||||||
commands::models::delete_model,
|
|
||||||
commands::models::cancel_download,
|
|
||||||
commands::models::set_active_model,
|
|
||||||
commands::models::get_current_model,
|
|
||||||
commands::models::get_transcription_model_status,
|
|
||||||
commands::models::is_model_loading,
|
|
||||||
commands::models::has_any_models_available,
|
|
||||||
commands::models::has_any_models_or_downloads,
|
|
||||||
commands::models::get_recommended_first_model,
|
|
||||||
commands::audio::update_microphone_mode,
|
|
||||||
commands::audio::get_microphone_mode,
|
|
||||||
commands::audio::get_available_microphones,
|
|
||||||
commands::audio::set_selected_microphone,
|
|
||||||
commands::audio::get_selected_microphone,
|
|
||||||
commands::audio::get_available_output_devices,
|
|
||||||
commands::audio::set_selected_output_device,
|
|
||||||
commands::audio::get_selected_output_device,
|
|
||||||
commands::audio::play_test_sound,
|
|
||||||
commands::audio::check_custom_sounds,
|
|
||||||
commands::audio::set_clamshell_microphone,
|
|
||||||
commands::audio::get_clamshell_microphone,
|
|
||||||
helpers::clamshell::is_clamshell,
|
|
||||||
helpers::clamshell::is_laptop,
|
|
||||||
commands::transcription::set_model_unload_timeout,
|
|
||||||
commands::transcription::get_model_load_status,
|
|
||||||
commands::transcription::unload_model_manually,
|
|
||||||
commands::history::get_history_entries,
|
|
||||||
commands::history::toggle_history_entry_saved,
|
|
||||||
commands::history::get_audio_file_path,
|
|
||||||
commands::history::delete_history_entry,
|
|
||||||
commands::history::update_history_limit,
|
|
||||||
commands::history::update_recording_retention_period
|
|
||||||
])
|
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use chrono::{DateTime, Local, Utc};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tauri::{AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
|
|
@ -10,7 +11,7 @@ use tauri_plugin_sql::{Migration, MigrationKind};
|
||||||
|
|
||||||
use crate::audio_toolkit::save_wav_file;
|
use crate::audio_toolkit::save_wav_file;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
|
||||||
pub struct HistoryEntry {
|
pub struct HistoryEntry {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use flate2::read::GzDecoder;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
@ -13,13 +14,13 @@ use std::sync::Mutex;
|
||||||
use tar::Archive;
|
use tar::Archive;
|
||||||
use tauri::{AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
pub enum EngineType {
|
pub enum EngineType {
|
||||||
Whisper,
|
Whisper,
|
||||||
Parakeet,
|
Parakeet,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
pub struct ModelInfo {
|
pub struct ModelInfo {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -36,7 +37,7 @@ pub struct ModelInfo {
|
||||||
pub speed_score: f32, // 0.0 to 1.0, higher is faster
|
pub speed_score: f32, // 0.0 to 1.0, higher is faster
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
pub struct DownloadProgress {
|
pub struct DownloadProgress {
|
||||||
pub model_id: String,
|
pub model_id: String,
|
||||||
pub downloaded: u64,
|
pub downloaded: u64,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,79 @@
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::de::{self, Visitor};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri_plugin_log::LogLevel;
|
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum LogLevel {
|
||||||
|
Trace,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom deserializer to handle both old numeric format (1-5) and new string format ("trace", "debug", etc.)
|
||||||
|
impl<'de> Deserialize<'de> for LogLevel {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct LogLevelVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for LogLevelVisitor {
|
||||||
|
type Value = LogLevel;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a string or integer representing log level")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, value: &str) -> Result<LogLevel, E> {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"trace" => Ok(LogLevel::Trace),
|
||||||
|
"debug" => Ok(LogLevel::Debug),
|
||||||
|
"info" => Ok(LogLevel::Info),
|
||||||
|
"warn" => Ok(LogLevel::Warn),
|
||||||
|
"error" => Ok(LogLevel::Error),
|
||||||
|
_ => Err(E::unknown_variant(
|
||||||
|
value,
|
||||||
|
&["trace", "debug", "info", "warn", "error"],
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E: de::Error>(self, value: u64) -> Result<LogLevel, E> {
|
||||||
|
match value {
|
||||||
|
1 => Ok(LogLevel::Trace),
|
||||||
|
2 => Ok(LogLevel::Debug),
|
||||||
|
3 => Ok(LogLevel::Info),
|
||||||
|
4 => Ok(LogLevel::Warn),
|
||||||
|
5 => Ok(LogLevel::Error),
|
||||||
|
_ => Err(E::invalid_value(de::Unexpected::Unsigned(value), &"1-5")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(LogLevelVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LogLevel> for tauri_plugin_log::LogLevel {
|
||||||
|
fn from(level: LogLevel) -> Self {
|
||||||
|
match level {
|
||||||
|
LogLevel::Trace => tauri_plugin_log::LogLevel::Trace,
|
||||||
|
LogLevel::Debug => tauri_plugin_log::LogLevel::Debug,
|
||||||
|
LogLevel::Info => tauri_plugin_log::LogLevel::Info,
|
||||||
|
LogLevel::Warn => tauri_plugin_log::LogLevel::Warn,
|
||||||
|
LogLevel::Error => tauri_plugin_log::LogLevel::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
||||||
pub struct ShortcutBinding {
|
pub struct ShortcutBinding {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -14,14 +82,14 @@ pub struct ShortcutBinding {
|
||||||
pub current_binding: String,
|
pub current_binding: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
||||||
pub struct LLMPrompt {
|
pub struct LLMPrompt {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub prompt: String,
|
pub prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
||||||
pub struct PostProcessProvider {
|
pub struct PostProcessProvider {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
|
@ -32,7 +100,7 @@ pub struct PostProcessProvider {
|
||||||
pub models_endpoint: Option<String>,
|
pub models_endpoint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum OverlayPosition {
|
pub enum OverlayPosition {
|
||||||
None,
|
None,
|
||||||
|
|
@ -40,7 +108,7 @@ pub enum OverlayPosition {
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ModelUnloadTimeout {
|
pub enum ModelUnloadTimeout {
|
||||||
Never,
|
Never,
|
||||||
|
|
@ -53,23 +121,22 @@ pub enum ModelUnloadTimeout {
|
||||||
Sec5, // Debug mode only
|
Sec5, // Debug mode only
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PasteMethod {
|
pub enum PasteMethod {
|
||||||
CtrlV,
|
CtrlV,
|
||||||
Direct,
|
Direct,
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
ShiftInsert,
|
ShiftInsert,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ClipboardHandling {
|
pub enum ClipboardHandling {
|
||||||
DontModify,
|
DontModify,
|
||||||
CopyToClipboard,
|
CopyToClipboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RecordingRetentionPeriod {
|
pub enum RecordingRetentionPeriod {
|
||||||
Never,
|
Never,
|
||||||
|
|
@ -125,7 +192,7 @@ impl ModelUnloadTimeout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum SoundTheme {
|
pub enum SoundTheme {
|
||||||
Marimba,
|
Marimba,
|
||||||
|
|
@ -152,7 +219,7 @@ impl SoundTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* still handy for composing the initial JSON in the store ------------- */
|
/* still handy for composing the initial JSON in the store ------------- */
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
||||||
pub struct AppSettings {
|
pub struct AppSettings {
|
||||||
pub bindings: HashMap<String, ShortcutBinding>,
|
pub bindings: HashMap<String, ShortcutBinding>,
|
||||||
pub push_to_talk: bool,
|
pub push_to_talk: bool,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use specta::Type;
|
||||||
use tauri::{AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use tauri_plugin_autostart::ManagerExt;
|
use tauri_plugin_autostart::ManagerExt;
|
||||||
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
|
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
|
||||||
|
|
@ -22,7 +23,7 @@ pub fn init_shortcuts(app: &AppHandle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Type)]
|
||||||
pub struct BindingResponse {
|
pub struct BindingResponse {
|
||||||
success: bool,
|
success: bool,
|
||||||
binding: Option<ShortcutBinding>,
|
binding: Option<ShortcutBinding>,
|
||||||
|
|
@ -30,6 +31,7 @@ pub struct BindingResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_binding(
|
pub fn change_binding(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
id: String,
|
id: String,
|
||||||
|
|
@ -93,6 +95,7 @@ pub fn change_binding(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn reset_binding(app: AppHandle, id: String) -> Result<BindingResponse, String> {
|
pub fn reset_binding(app: AppHandle, id: String) -> Result<BindingResponse, String> {
|
||||||
let binding = settings::get_stored_binding(&app, &id);
|
let binding = settings::get_stored_binding(&app, &id);
|
||||||
|
|
||||||
|
|
@ -100,6 +103,7 @@ pub fn reset_binding(app: AppHandle, id: String) -> Result<BindingResponse, Stri
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_ptt_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_ptt_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
|
|
||||||
|
|
@ -113,6 +117,7 @@ pub fn change_ptt_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_audio_feedback_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_audio_feedback_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.audio_feedback = enabled;
|
settings.audio_feedback = enabled;
|
||||||
|
|
@ -121,6 +126,7 @@ pub fn change_audio_feedback_setting(app: AppHandle, enabled: bool) -> Result<()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_audio_feedback_volume_setting(app: AppHandle, volume: f32) -> Result<(), String> {
|
pub fn change_audio_feedback_volume_setting(app: AppHandle, volume: f32) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.audio_feedback_volume = volume;
|
settings.audio_feedback_volume = volume;
|
||||||
|
|
@ -129,6 +135,7 @@ pub fn change_audio_feedback_volume_setting(app: AppHandle, volume: f32) -> Resu
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_sound_theme_setting(app: AppHandle, theme: String) -> Result<(), String> {
|
pub fn change_sound_theme_setting(app: AppHandle, theme: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
let parsed = match theme.as_str() {
|
let parsed = match theme.as_str() {
|
||||||
|
|
@ -146,6 +153,7 @@ pub fn change_sound_theme_setting(app: AppHandle, theme: String) -> Result<(), S
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_translate_to_english_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_translate_to_english_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.translate_to_english = enabled;
|
settings.translate_to_english = enabled;
|
||||||
|
|
@ -154,6 +162,7 @@ pub fn change_translate_to_english_setting(app: AppHandle, enabled: bool) -> Res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_selected_language_setting(app: AppHandle, language: String) -> Result<(), String> {
|
pub fn change_selected_language_setting(app: AppHandle, language: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.selected_language = language;
|
settings.selected_language = language;
|
||||||
|
|
@ -162,6 +171,7 @@ pub fn change_selected_language_setting(app: AppHandle, language: String) -> Res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_overlay_position_setting(app: AppHandle, position: String) -> Result<(), String> {
|
pub fn change_overlay_position_setting(app: AppHandle, position: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
let parsed = match position.as_str() {
|
let parsed = match position.as_str() {
|
||||||
|
|
@ -183,6 +193,7 @@ pub fn change_overlay_position_setting(app: AppHandle, position: String) -> Resu
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_debug_mode_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_debug_mode_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.debug_mode = enabled;
|
settings.debug_mode = enabled;
|
||||||
|
|
@ -201,6 +212,7 @@ pub fn change_debug_mode_setting(app: AppHandle, enabled: bool) -> Result<(), St
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_start_hidden_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_start_hidden_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.start_hidden = enabled;
|
settings.start_hidden = enabled;
|
||||||
|
|
@ -219,6 +231,7 @@ pub fn change_start_hidden_setting(app: AppHandle, enabled: bool) -> Result<(),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_autostart_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_autostart_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.autostart_enabled = enabled;
|
settings.autostart_enabled = enabled;
|
||||||
|
|
@ -245,6 +258,7 @@ pub fn change_autostart_setting(app: AppHandle, enabled: bool) -> Result<(), Str
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn update_custom_words(app: AppHandle, words: Vec<String>) -> Result<(), String> {
|
pub fn update_custom_words(app: AppHandle, words: Vec<String>) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.custom_words = words;
|
settings.custom_words = words;
|
||||||
|
|
@ -253,6 +267,7 @@ pub fn update_custom_words(app: AppHandle, words: Vec<String>) -> Result<(), Str
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_word_correction_threshold_setting(
|
pub fn change_word_correction_threshold_setting(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
threshold: f64,
|
threshold: f64,
|
||||||
|
|
@ -264,12 +279,12 @@ pub fn change_word_correction_threshold_setting(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(), String> {
|
pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
let parsed = match method.as_str() {
|
let parsed = match method.as_str() {
|
||||||
"ctrl_v" => PasteMethod::CtrlV,
|
"ctrl_v" => PasteMethod::CtrlV,
|
||||||
"direct" => PasteMethod::Direct,
|
"direct" => PasteMethod::Direct,
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
"shift_insert" => PasteMethod::ShiftInsert,
|
"shift_insert" => PasteMethod::ShiftInsert,
|
||||||
other => {
|
other => {
|
||||||
warn!("Invalid paste method '{}', defaulting to ctrl_v", other);
|
warn!("Invalid paste method '{}', defaulting to ctrl_v", other);
|
||||||
|
|
@ -282,6 +297,7 @@ pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Result<(), String> {
|
pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
let parsed = match handling.as_str() {
|
let parsed = match handling.as_str() {
|
||||||
|
|
@ -301,6 +317,7 @@ pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Re
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_post_process_enabled_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_post_process_enabled_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.post_process_enabled = enabled;
|
settings.post_process_enabled = enabled;
|
||||||
|
|
@ -309,6 +326,7 @@ pub fn change_post_process_enabled_setting(app: AppHandle, enabled: bool) -> Res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_post_process_base_url_setting(
|
pub fn change_post_process_base_url_setting(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
provider_id: String,
|
provider_id: String,
|
||||||
|
|
@ -352,6 +370,7 @@ fn validate_provider_exists(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_post_process_api_key_setting(
|
pub fn change_post_process_api_key_setting(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
provider_id: String,
|
provider_id: String,
|
||||||
|
|
@ -365,6 +384,7 @@ pub fn change_post_process_api_key_setting(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_post_process_model_setting(
|
pub fn change_post_process_model_setting(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
provider_id: String,
|
provider_id: String,
|
||||||
|
|
@ -378,6 +398,7 @@ pub fn change_post_process_model_setting(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_post_process_provider(app: AppHandle, provider_id: String) -> Result<(), String> {
|
pub fn set_post_process_provider(app: AppHandle, provider_id: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
validate_provider_exists(&settings, &provider_id)?;
|
validate_provider_exists(&settings, &provider_id)?;
|
||||||
|
|
@ -387,6 +408,7 @@ pub fn set_post_process_provider(app: AppHandle, provider_id: String) -> Result<
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn add_post_process_prompt(
|
pub fn add_post_process_prompt(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -410,6 +432,7 @@ pub fn add_post_process_prompt(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn update_post_process_prompt(
|
pub fn update_post_process_prompt(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
id: String,
|
id: String,
|
||||||
|
|
@ -433,6 +456,7 @@ pub fn update_post_process_prompt(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn delete_post_process_prompt(app: AppHandle, id: String) -> Result<(), String> {
|
pub fn delete_post_process_prompt(app: AppHandle, id: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
|
|
||||||
|
|
@ -460,6 +484,7 @@ pub fn delete_post_process_prompt(app: AppHandle, id: String) -> Result<(), Stri
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn fetch_post_process_models(
|
pub async fn fetch_post_process_models(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
provider_id: String,
|
provider_id: String,
|
||||||
|
|
@ -599,6 +624,7 @@ async fn fetch_models_manual(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_post_process_selected_prompt(app: AppHandle, id: String) -> Result<(), String> {
|
pub fn set_post_process_selected_prompt(app: AppHandle, id: String) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
|
|
||||||
|
|
@ -613,6 +639,7 @@ pub fn set_post_process_selected_prompt(app: AppHandle, id: String) -> Result<()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn change_mute_while_recording_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn change_mute_while_recording_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
settings.mute_while_recording = enabled;
|
settings.mute_while_recording = enabled;
|
||||||
|
|
@ -642,6 +669,7 @@ fn validate_shortcut_string(raw: &str) -> Result<(), String> {
|
||||||
/// Temporarily unregister a binding while the user is editing it in the UI.
|
/// Temporarily unregister a binding while the user is editing it in the UI.
|
||||||
/// This avoids firing the action while keys are being recorded.
|
/// This avoids firing the action while keys are being recorded.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn suspend_binding(app: AppHandle, id: String) -> Result<(), String> {
|
pub fn suspend_binding(app: AppHandle, id: String) -> Result<(), String> {
|
||||||
if let Some(b) = settings::get_bindings(&app).get(&id).cloned() {
|
if let Some(b) = settings::get_bindings(&app).get(&id).cloned() {
|
||||||
if let Err(e) = _unregister_shortcut(&app, b) {
|
if let Err(e) = _unregister_shortcut(&app, b) {
|
||||||
|
|
@ -654,6 +682,7 @@ pub fn suspend_binding(app: AppHandle, id: String) -> Result<(), String> {
|
||||||
|
|
||||||
/// Re-register the binding after the user has finished editing.
|
/// Re-register the binding after the user has finished editing.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn resume_binding(app: AppHandle, id: String) -> Result<(), String> {
|
pub fn resume_binding(app: AppHandle, id: String) -> Result<(), String> {
|
||||||
if let Some(b) = settings::get_bindings(&app).get(&id).cloned() {
|
if let Some(b) = settings::get_bindings(&app).get(&id).cloned() {
|
||||||
if let Err(e) = _register_shortcut(&app, b) {
|
if let Err(e) = _register_shortcut(&app, b) {
|
||||||
|
|
|
||||||
10
src/App.tsx
10
src/App.tsx
|
|
@ -1,4 +1,3 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
@ -7,6 +6,7 @@ import Footer from "./components/footer";
|
||||||
import Onboarding from "./components/onboarding";
|
import Onboarding from "./components/onboarding";
|
||||||
import { Sidebar, SidebarSection, SECTIONS_CONFIG } from "./components/Sidebar";
|
import { Sidebar, SidebarSection, SECTIONS_CONFIG } from "./components/Sidebar";
|
||||||
import { useSettings } from "./hooks/useSettings";
|
import { useSettings } from "./hooks/useSettings";
|
||||||
|
import { commands } from "@/bindings";
|
||||||
|
|
||||||
const renderSettingsContent = (section: SidebarSection) => {
|
const renderSettingsContent = (section: SidebarSection) => {
|
||||||
const ActiveComponent =
|
const ActiveComponent =
|
||||||
|
|
@ -52,8 +52,12 @@ function App() {
|
||||||
const checkOnboardingStatus = async () => {
|
const checkOnboardingStatus = async () => {
|
||||||
try {
|
try {
|
||||||
// Always check if they have any models available
|
// Always check if they have any models available
|
||||||
const modelsAvailable: boolean = await invoke("has_any_models_available");
|
const result = await commands.hasAnyModelsAvailable();
|
||||||
setShowOnboarding(!modelsAvailable);
|
if (result.status === "ok") {
|
||||||
|
setShowOnboarding(!result.data);
|
||||||
|
} else {
|
||||||
|
setShowOnboarding(true);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check onboarding status:", error);
|
console.error("Failed to check onboarding status:", error);
|
||||||
setShowOnboarding(true);
|
setShowOnboarding(true);
|
||||||
|
|
|
||||||
674
src/bindings.ts
Normal file
674
src/bindings.ts
Normal file
|
|
@ -0,0 +1,674 @@
|
||||||
|
|
||||||
|
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||||
|
|
||||||
|
/** user-defined commands **/
|
||||||
|
|
||||||
|
|
||||||
|
export const commands = {
|
||||||
|
async changeBinding(id: string, binding: string) : Promise<Result<BindingResponse, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_binding", { id, binding }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async resetBinding(id: string) : Promise<Result<BindingResponse, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("reset_binding", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePttSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_ptt_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeAudioFeedbackSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_audio_feedback_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeAudioFeedbackVolumeSetting(volume: number) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_audio_feedback_volume_setting", { volume }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeSoundThemeSetting(theme: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_sound_theme_setting", { theme }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeStartHiddenSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_start_hidden_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeAutostartSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_autostart_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeTranslateToEnglishSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_translate_to_english_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeSelectedLanguageSetting(language: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_selected_language_setting", { language }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeOverlayPositionSetting(position: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_overlay_position_setting", { position }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeDebugModeSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_debug_mode_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeWordCorrectionThresholdSetting(threshold: number) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_word_correction_threshold_setting", { threshold }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePasteMethodSetting(method: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_paste_method_setting", { method }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeClipboardHandlingSetting(handling: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_clipboard_handling_setting", { handling }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePostProcessEnabledSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_post_process_enabled_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePostProcessBaseUrlSetting(providerId: string, baseUrl: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_post_process_base_url_setting", { providerId, baseUrl }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePostProcessApiKeySetting(providerId: string, apiKey: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_post_process_api_key_setting", { providerId, apiKey }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePostProcessModelSetting(providerId: string, model: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_post_process_model_setting", { providerId, model }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setPostProcessProvider(providerId: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_post_process_provider", { providerId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchPostProcessModels(providerId: string) : Promise<Result<string[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("fetch_post_process_models", { providerId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async addPostProcessPrompt(name: string, prompt: string) : Promise<Result<LLMPrompt, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("add_post_process_prompt", { name, prompt }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updatePostProcessPrompt(id: string, name: string, prompt: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("update_post_process_prompt", { id, name, prompt }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deletePostProcessPrompt(id: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("delete_post_process_prompt", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setPostProcessSelectedPrompt(id: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_post_process_selected_prompt", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateCustomWords(words: string[]) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("update_custom_words", { words }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Temporarily unregister a binding while the user is editing it in the UI.
|
||||||
|
* This avoids firing the action while keys are being recorded.
|
||||||
|
*/
|
||||||
|
async suspendBinding(id: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("suspend_binding", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Re-register the binding after the user has finished editing.
|
||||||
|
*/
|
||||||
|
async resumeBinding(id: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("resume_binding", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeMuteWhileRecordingSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_mute_while_recording_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async triggerUpdateCheck() : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("trigger_update_check") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async cancelOperation() : Promise<void> {
|
||||||
|
await TAURI_INVOKE("cancel_operation");
|
||||||
|
},
|
||||||
|
async getAppDirPath() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_app_dir_path") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAppSettings() : Promise<Result<AppSettings, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_app_settings") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getDefaultSettings() : Promise<Result<AppSettings, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_default_settings") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getLogDirPath() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_log_dir_path") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setLogLevel(level: LogLevel) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_log_level", { level }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openRecordingsFolder() : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("open_recordings_folder") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openLogDir() : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("open_log_dir") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openAppDataDir() : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("open_app_data_dir") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAvailableModels() : Promise<Result<ModelInfo[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_available_models") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getModelInfo(modelId: string) : Promise<Result<ModelInfo | null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_model_info", { modelId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async downloadModel(modelId: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("download_model", { modelId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteModel(modelId: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("delete_model", { modelId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async cancelDownload(modelId: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("cancel_download", { modelId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setActiveModel(modelId: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_active_model", { modelId }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getCurrentModel() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_current_model") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getTranscriptionModelStatus() : Promise<Result<string | null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_transcription_model_status") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async isModelLoading() : Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("is_model_loading") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async hasAnyModelsAvailable() : Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("has_any_models_available") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async hasAnyModelsOrDownloads() : Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("has_any_models_or_downloads") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getRecommendedFirstModel() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_recommended_first_model") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateMicrophoneMode(alwaysOn: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("update_microphone_mode", { alwaysOn }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getMicrophoneMode() : Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_microphone_mode") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAvailableMicrophones() : Promise<Result<AudioDevice[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_available_microphones") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setSelectedMicrophone(deviceName: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_selected_microphone", { deviceName }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getSelectedMicrophone() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_selected_microphone") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAvailableOutputDevices() : Promise<Result<AudioDevice[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_available_output_devices") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setSelectedOutputDevice(deviceName: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_selected_output_device", { deviceName }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getSelectedOutputDevice() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_selected_output_device") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async playTestSound(soundType: string) : Promise<void> {
|
||||||
|
await TAURI_INVOKE("play_test_sound", { soundType });
|
||||||
|
},
|
||||||
|
async checkCustomSounds() : Promise<CustomSounds> {
|
||||||
|
return await TAURI_INVOKE("check_custom_sounds");
|
||||||
|
},
|
||||||
|
async setClamshellMicrophone(deviceName: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("set_clamshell_microphone", { deviceName }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getClamshellMicrophone() : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_clamshell_microphone") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setModelUnloadTimeout(timeout: ModelUnloadTimeout) : Promise<void> {
|
||||||
|
await TAURI_INVOKE("set_model_unload_timeout", { timeout });
|
||||||
|
},
|
||||||
|
async getModelLoadStatus() : Promise<Result<ModelLoadStatus, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_model_load_status") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async unloadModelManually() : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("unload_model_manually") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getHistoryEntries() : Promise<Result<HistoryEntry[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_history_entries") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async toggleHistoryEntrySaved(id: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("toggle_history_entry_saved", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAudioFilePath(fileName: string) : Promise<Result<string, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_audio_file_path", { fileName }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteHistoryEntry(id: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("delete_history_entry", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateHistoryLimit(limit: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("update_history_limit", { limit }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateRecordingRetentionPeriod(period: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("update_recording_retention_period", { period }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks if the Mac is a laptop by detecting battery presence
|
||||||
|
*
|
||||||
|
* This uses pmset to check for battery information.
|
||||||
|
* Returns true if a battery is detected (laptop), false otherwise (desktop)
|
||||||
|
*/
|
||||||
|
async isLaptop() : Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("is_laptop") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** user-defined events **/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** user-defined constants **/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** user-defined types **/
|
||||||
|
|
||||||
|
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: string; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean }
|
||||||
|
export type AudioDevice = { index: string; name: string; is_default: boolean }
|
||||||
|
export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null }
|
||||||
|
export type ClipboardHandling = "dont_modify" | "copy_to_clipboard"
|
||||||
|
export type CustomSounds = { start: boolean; stop: boolean }
|
||||||
|
export type EngineType = "Whisper" | "Parakeet"
|
||||||
|
export type HistoryEntry = { id: string; file_name: string; timestamp: string; saved: boolean; title: string; transcription_text: string; post_processed_text: string | null; post_process_prompt: string | null }
|
||||||
|
export type LLMPrompt = { id: string; name: string; prompt: string }
|
||||||
|
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error"
|
||||||
|
export type ModelInfo = { id: string; name: string; description: string; filename: string; url: string | null; size_mb: string; is_downloaded: boolean; is_downloading: boolean; partial_size: string; is_directory: boolean; engine_type: EngineType; accuracy_score: number; speed_score: number }
|
||||||
|
export type ModelLoadStatus = { is_loaded: boolean; current_model: string | null }
|
||||||
|
export type ModelUnloadTimeout = "never" | "immediately" | "min_2" | "min_5" | "min_10" | "min_15" | "hour_1" | "sec_5"
|
||||||
|
export type OverlayPosition = "none" | "top" | "bottom"
|
||||||
|
export type PasteMethod = "ctrl_v" | "direct" | "shift_insert"
|
||||||
|
export type PostProcessProvider = { id: string; label: string; base_url: string; allow_base_url_edit?: boolean; models_endpoint?: string | null }
|
||||||
|
export type RecordingRetentionPeriod = "never" | "preserve_limit" | "days_3" | "weeks_2" | "months_3"
|
||||||
|
export type ShortcutBinding = { id: string; name: string; description: string; default_binding: string; current_binding: string }
|
||||||
|
export type SoundTheme = "marimba" | "pop" | "custom"
|
||||||
|
|
||||||
|
/** tauri-specta globals **/
|
||||||
|
|
||||||
|
import {
|
||||||
|
invoke as TAURI_INVOKE,
|
||||||
|
Channel as TAURI_CHANNEL,
|
||||||
|
} from "@tauri-apps/api/core";
|
||||||
|
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
|
||||||
|
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
|
||||||
|
|
||||||
|
type __EventObj__<T> = {
|
||||||
|
listen: (
|
||||||
|
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||||
|
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||||
|
once: (
|
||||||
|
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||||
|
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||||
|
emit: null extends T
|
||||||
|
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||||
|
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result<T, E> =
|
||||||
|
| { status: "ok"; data: T }
|
||||||
|
| { status: "error"; error: E };
|
||||||
|
|
||||||
|
function __makeEvents__<T extends Record<string, any>>(
|
||||||
|
mappings: Record<keyof T, string>,
|
||||||
|
) {
|
||||||
|
return new Proxy(
|
||||||
|
{} as unknown as {
|
||||||
|
[K in keyof T]: __EventObj__<T[K]> & {
|
||||||
|
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get: (_, event) => {
|
||||||
|
const name = mappings[event as keyof T];
|
||||||
|
|
||||||
|
return new Proxy((() => {}) as any, {
|
||||||
|
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||||
|
listen: (arg: any) => window.listen(name, arg),
|
||||||
|
once: (arg: any) => window.once(name, arg),
|
||||||
|
emit: (arg: any) => window.emit(name, arg),
|
||||||
|
}),
|
||||||
|
get: (_, command: keyof __EventObj__<any>) => {
|
||||||
|
switch (command) {
|
||||||
|
case "listen":
|
||||||
|
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
||||||
|
case "once":
|
||||||
|
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
||||||
|
case "emit":
|
||||||
|
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ModelInfo } from "../../lib/types";
|
import type { ModelInfo } from "@/bindings";
|
||||||
import { formatModelSize } from "../../lib/utils/format";
|
import { formatModelSize } from "../../lib/utils/format";
|
||||||
import { ProgressBar } from "../shared";
|
import { ProgressBar } from "../shared";
|
||||||
|
|
||||||
|
|
@ -180,7 +180,7 @@ const ModelDropdown: React.FC<ModelDropdownProps> = ({
|
||||||
{model.description}
|
{model.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-xs text-text/50 tabular-nums">
|
<div className="mt-1 text-xs text-text/50 tabular-nums">
|
||||||
Download size · {formatModelSize(model.size_mb)}
|
Download size · {formatModelSize(Number(model.size_mb))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-logo-primary tabular-nums">
|
<div className="text-xs text-logo-primary tabular-nums">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { ModelInfo } from "../../lib/types";
|
import { commands, type ModelInfo } from "@/bindings";
|
||||||
import ModelStatusButton from "./ModelStatusButton";
|
import ModelStatusButton from "./ModelStatusButton";
|
||||||
import ModelDropdown from "./ModelDropdown";
|
import ModelDropdown from "./ModelDropdown";
|
||||||
import DownloadProgressDisplay from "./DownloadProgressDisplay";
|
import DownloadProgressDisplay from "./DownloadProgressDisplay";
|
||||||
|
|
@ -238,8 +237,10 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
|
|
||||||
const loadModels = async () => {
|
const loadModels = async () => {
|
||||||
try {
|
try {
|
||||||
const modelList = await invoke<ModelInfo[]>("get_available_models");
|
const result = await commands.getAvailableModels();
|
||||||
setModels(modelList);
|
if (result.status === "ok") {
|
||||||
|
setModels(result.data);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load models:", err);
|
console.error("Failed to load models:", err);
|
||||||
}
|
}
|
||||||
|
|
@ -247,21 +248,25 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
|
|
||||||
const loadCurrentModel = async () => {
|
const loadCurrentModel = async () => {
|
||||||
try {
|
try {
|
||||||
const current = await invoke<string>("get_current_model");
|
const result = await commands.getCurrentModel();
|
||||||
setCurrentModelId(current);
|
if (result.status === "ok") {
|
||||||
|
const current = result.data;
|
||||||
|
setCurrentModelId(current);
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
// Check if model is actually loaded
|
// Check if model is actually loaded
|
||||||
const transcriptionStatus = await invoke<string | null>(
|
const statusResult = await commands.getTranscriptionModelStatus();
|
||||||
"get_transcription_model_status",
|
if (statusResult.status === "ok") {
|
||||||
);
|
const transcriptionStatus = statusResult.data;
|
||||||
if (transcriptionStatus === current) {
|
if (transcriptionStatus === current) {
|
||||||
setModelStatus("ready");
|
setModelStatus("ready");
|
||||||
|
} else {
|
||||||
|
setModelStatus("unloaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setModelStatus("unloaded");
|
setModelStatus("none");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setModelStatus("none");
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load current model:", err);
|
console.error("Failed to load current model:", err);
|
||||||
|
|
@ -272,10 +277,16 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
|
|
||||||
const handleModelSelect = async (modelId: string) => {
|
const handleModelSelect = async (modelId: string) => {
|
||||||
try {
|
try {
|
||||||
|
setCurrentModelId(modelId); // Set optimistically so loading text shows correct model
|
||||||
setModelError(null);
|
setModelError(null);
|
||||||
setShowModelDropdown(false);
|
setShowModelDropdown(false);
|
||||||
await invoke("set_active_model", { modelId });
|
const result = await commands.setActiveModel(modelId);
|
||||||
setCurrentModelId(modelId);
|
if (result.status === "error") {
|
||||||
|
const errorMsg = result.error;
|
||||||
|
setModelError(errorMsg);
|
||||||
|
setModelStatus("error");
|
||||||
|
onError?.(errorMsg);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = `${err}`;
|
const errorMsg = `${err}`;
|
||||||
setModelError(errorMsg);
|
setModelError(errorMsg);
|
||||||
|
|
@ -287,7 +298,13 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
const handleModelDownload = async (modelId: string) => {
|
const handleModelDownload = async (modelId: string) => {
|
||||||
try {
|
try {
|
||||||
setModelError(null);
|
setModelError(null);
|
||||||
await invoke("download_model", { modelId });
|
const result = await commands.downloadModel(modelId);
|
||||||
|
if (result.status === "error") {
|
||||||
|
const errorMsg = result.error;
|
||||||
|
setModelError(errorMsg);
|
||||||
|
setModelStatus("error");
|
||||||
|
onError?.(errorMsg);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = `${err}`;
|
const errorMsg = `${err}`;
|
||||||
setModelError(errorMsg);
|
setModelError(errorMsg);
|
||||||
|
|
@ -347,9 +364,11 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModelDelete = async (modelId: string) => {
|
const handleModelDelete = async (modelId: string) => {
|
||||||
await invoke("delete_model", { modelId });
|
const result = await commands.deleteModel(modelId);
|
||||||
await loadModels();
|
if (result.status === "ok") {
|
||||||
setModelError(null);
|
await loadModels();
|
||||||
|
setModelError(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Download } from "lucide-react";
|
import { Download } from "lucide-react";
|
||||||
import { ModelInfo } from "../../lib/types";
|
import type { ModelInfo } from "@/bindings";
|
||||||
import { formatModelSize } from "../../lib/utils/format";
|
import { formatModelSize } from "../../lib/utils/format";
|
||||||
import Badge from "../ui/Badge";
|
import Badge from "../ui/Badge";
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ const ModelCard: React.FC<ModelCardProps> = ({
|
||||||
<h3 className="text-lg font-semibold text-text group-hover:text-logo-primary transition-colors">
|
<h3 className="text-lg font-semibold text-text group-hover:text-logo-primary transition-colors">
|
||||||
{model.name}
|
{model.name}
|
||||||
</h3>
|
</h3>
|
||||||
<DownloadSize sizeMb={model.size_mb} />
|
<DownloadSize sizeMb={Number(model.size_mb)} />
|
||||||
{isFeatured && <Badge variant="primary">Recommended</Badge>}
|
{isFeatured && <Badge variant="primary">Recommended</Badge>}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-text/60 text-sm leading-relaxed">
|
<p className="text-text/60 text-sm leading-relaxed">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands, type ModelInfo } from "@/bindings";
|
||||||
import { ModelInfo } from "../../lib/types";
|
|
||||||
import ModelCard from "./ModelCard";
|
import ModelCard from "./ModelCard";
|
||||||
import HandyTextLogo from "../icons/HandyTextLogo";
|
import HandyTextLogo from "../icons/HandyTextLogo";
|
||||||
|
|
||||||
|
|
@ -19,9 +18,13 @@ const Onboarding: React.FC<OnboardingProps> = ({ onModelSelected }) => {
|
||||||
|
|
||||||
const loadModels = async () => {
|
const loadModels = async () => {
|
||||||
try {
|
try {
|
||||||
const models: ModelInfo[] = await invoke("get_available_models");
|
const result = await commands.getAvailableModels();
|
||||||
// Only show downloadable models for onboarding
|
if (result.status === "ok") {
|
||||||
setAvailableModels(models.filter((m) => !m.is_downloaded));
|
// Only show downloadable models for onboarding
|
||||||
|
setAvailableModels(result.data.filter((m) => !m.is_downloaded));
|
||||||
|
} else {
|
||||||
|
setError("Failed to load available models");
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load models:", err);
|
console.error("Failed to load models:", err);
|
||||||
setError("Failed to load available models");
|
setError("Failed to load available models");
|
||||||
|
|
@ -36,7 +39,12 @@ const Onboarding: React.FC<OnboardingProps> = ({ onModelSelected }) => {
|
||||||
onModelSelected();
|
onModelSelected();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("download_model", { modelId });
|
const result = await commands.downloadModel(modelId);
|
||||||
|
if (result.status === "error") {
|
||||||
|
console.error("Download failed:", result.error);
|
||||||
|
setError(`Failed to download model: ${result.error}`);
|
||||||
|
setDownloading(false);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Download failed:", err);
|
console.error("Download failed:", err);
|
||||||
setError(`Failed to download model: ${err}`);
|
setError(`Failed to download model: ${err}`);
|
||||||
|
|
@ -80,7 +88,7 @@ const Onboarding: React.FC<OnboardingProps> = ({ onModelSelected }) => {
|
||||||
|
|
||||||
{availableModels
|
{availableModels
|
||||||
.filter((model) => !getRecommendedBadge(model.id))
|
.filter((model) => !getRecommendedBadge(model.id))
|
||||||
.sort((a, b) => a.size_mb - b.size_mb)
|
.sort((a, b) => Number(a.size_mb) - Number(b.size_mb))
|
||||||
.map((model) => (
|
.map((model) => (
|
||||||
<ModelCard
|
<ModelCard
|
||||||
key={model.id}
|
key={model.id}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "@/bindings";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { Button } from "../ui/Button";
|
import { Button } from "../ui/Button";
|
||||||
|
|
||||||
|
|
@ -19,8 +19,12 @@ export const AppDataDirectory: React.FC<AppDataDirectoryProps> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadAppDirectory = async () => {
|
const loadAppDirectory = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await invoke<string>("get_app_dir_path");
|
const result = await commands.getAppDirPath();
|
||||||
setAppDirPath(result);
|
if (result.status === "ok") {
|
||||||
|
setAppDirPath(result.data);
|
||||||
|
} else {
|
||||||
|
setError(result.error);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error ? err.message : "Failed to load app directory",
|
err instanceof Error ? err.message : "Failed to load app directory",
|
||||||
|
|
@ -36,7 +40,7 @@ export const AppDataDirectory: React.FC<AppDataDirectoryProps> = ({
|
||||||
const handleOpen = async () => {
|
const handleOpen = async () => {
|
||||||
if (!appDirPath) return;
|
if (!appDirPath) return;
|
||||||
try {
|
try {
|
||||||
await invoke("open_app_data_dir");
|
await commands.openAppDataDir();
|
||||||
} catch (openError) {
|
} catch (openError) {
|
||||||
console.error("Failed to open app data directory:", openError);
|
console.error("Failed to open app data directory:", openError);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "@/bindings";
|
||||||
import { Dropdown } from "../ui/Dropdown";
|
import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { ResetButton } from "../ui/ResetButton";
|
import { ResetButton } from "../ui/ResetButton";
|
||||||
|
|
@ -27,8 +27,12 @@ export const ClamshellMicrophoneSelector: React.FC<ClamshellMicrophoneSelectorPr
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkIsLaptop = async () => {
|
const checkIsLaptop = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await invoke<boolean>("is_laptop");
|
const result = await commands.isLaptop();
|
||||||
setIsLaptop(result);
|
if (result.status === "ok") {
|
||||||
|
setIsLaptop(result.data);
|
||||||
|
} else {
|
||||||
|
setIsLaptop(false);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check if device is laptop:", error);
|
console.error("Failed to check if device is laptop:", error);
|
||||||
setIsLaptop(false);
|
setIsLaptop(false);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import { Dropdown } from "../ui/Dropdown";
|
import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import type { ClipboardHandling } from "../../lib/types";
|
import type { ClipboardHandling } from "@/bindings";
|
||||||
|
|
||||||
interface ClipboardHandlingProps {
|
interface ClipboardHandlingProps {
|
||||||
descriptionMode?: "inline" | "tooltip";
|
descriptionMode?: "inline" | "tooltip";
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import { ResetButton } from "../ui/ResetButton";
|
import { ResetButton } from "../ui/ResetButton";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "@/bindings";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface HandyShortcutProps {
|
interface HandyShortcutProps {
|
||||||
|
|
@ -80,7 +80,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (editingShortcutId && originalBinding) {
|
if (editingShortcutId && originalBinding) {
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, originalBinding);
|
await updateBinding(editingShortcutId, originalBinding);
|
||||||
await invoke("resume_binding", { id: editingShortcutId }).catch(
|
await commands.resumeBinding(editingShortcutId).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -88,7 +88,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
toast.error("Failed to restore original shortcut");
|
toast.error("Failed to restore original shortcut");
|
||||||
}
|
}
|
||||||
} else if (editingShortcutId) {
|
} else if (editingShortcutId) {
|
||||||
await invoke("resume_binding", { id: editingShortcutId }).catch(
|
await commands.resumeBinding(editingShortcutId).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +134,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, newShortcut);
|
await updateBinding(editingShortcutId, newShortcut);
|
||||||
// Re-register the shortcut now that recording is finished
|
// Re-register the shortcut now that recording is finished
|
||||||
await invoke("resume_binding", { id: editingShortcutId }).catch(
|
await commands.resumeBinding(editingShortcutId).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -145,7 +145,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (originalBinding) {
|
if (originalBinding) {
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, originalBinding);
|
await updateBinding(editingShortcutId, originalBinding);
|
||||||
await invoke("resume_binding", { id: editingShortcutId }).catch(
|
await commands.resumeBinding(editingShortcutId).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
} catch (resetError) {
|
} catch (resetError) {
|
||||||
|
|
@ -173,7 +173,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (editingShortcutId && originalBinding) {
|
if (editingShortcutId && originalBinding) {
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, originalBinding);
|
await updateBinding(editingShortcutId, originalBinding);
|
||||||
await invoke("resume_binding", { id: editingShortcutId }).catch(
|
await commands.resumeBinding(editingShortcutId).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -181,7 +181,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
toast.error("Failed to restore original shortcut");
|
toast.error("Failed to restore original shortcut");
|
||||||
}
|
}
|
||||||
} else if (editingShortcutId) {
|
} else if (editingShortcutId) {
|
||||||
invoke("resume_binding", { id: editingShortcutId }).catch(
|
commands.resumeBinding(editingShortcutId).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +217,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (editingShortcutId === id) return; // Already editing this shortcut
|
if (editingShortcutId === id) return; // Already editing this shortcut
|
||||||
|
|
||||||
// Suspend current binding to avoid firing while recording
|
// Suspend current binding to avoid firing while recording
|
||||||
await invoke("suspend_binding", { id }).catch(console.error);
|
await commands.suspendBinding(id).catch(console.error);
|
||||||
|
|
||||||
// Store the original binding to restore if canceled
|
// Store the original binding to restore if canceled
|
||||||
setOriginalBinding(bindings[id]?.current_binding || "");
|
setOriginalBinding(bindings[id]?.current_binding || "");
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ export const HistoryLimit: React.FC<HistoryLimitProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
|
|
||||||
const historyLimit = getSetting("history_limit") ?? 5;
|
const historyLimit = Number(getSetting("history_limit") ?? "5");
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = parseInt(event.target.value, 10);
|
const value = parseInt(event.target.value, 10);
|
||||||
if (!isNaN(value) && value >= 0) {
|
if (!isNaN(value) && value >= 0) {
|
||||||
updateSetting("history_limit", value);
|
updateSetting("history_limit", value.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import { ModelUnloadTimeout } from "../../lib/types";
|
import { commands, type ModelUnloadTimeout } from "@/bindings";
|
||||||
import { Dropdown } from "../ui/Dropdown";
|
import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
|
|
||||||
|
|
@ -35,7 +34,7 @@ export const ModelUnloadTimeoutSetting: React.FC<ModelUnloadTimeoutProps> = ({
|
||||||
const newTimeout = event.target.value as ModelUnloadTimeout;
|
const newTimeout = event.target.value as ModelUnloadTimeout;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("set_model_unload_timeout", { timeout: newTimeout });
|
await commands.setModelUnloadTimeout(newTimeout);
|
||||||
updateSetting("model_unload_timeout", newTimeout);
|
updateSetting("model_unload_timeout", newTimeout);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update model unload timeout:", error);
|
console.error("Failed to update model unload timeout:", error);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { ResetButton } from "../ui/ResetButton";
|
import { ResetButton } from "../ui/ResetButton";
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import { AudioDevice } from "../../lib/types";
|
import type { AudioDevice } from "@/bindings";
|
||||||
|
|
||||||
interface OutputDeviceSelectorProps {
|
interface OutputDeviceSelectorProps {
|
||||||
descriptionMode?: "inline" | "tooltip";
|
descriptionMode?: "inline" | "tooltip";
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { type as getOsType } from "@tauri-apps/plugin-os";
|
||||||
import { Dropdown } from "../ui/Dropdown";
|
import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import type { PasteMethod } from "../../lib/types";
|
import type { PasteMethod } from "@/bindings";
|
||||||
|
|
||||||
interface PasteMethodProps {
|
interface PasteMethodProps {
|
||||||
descriptionMode?: "inline" | "tooltip";
|
descriptionMode?: "inline" | "tooltip";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
import { useSettings } from "../../../hooks/useSettings";
|
import { useSettings } from "../../../hooks/useSettings";
|
||||||
import { useSettingsStore } from "../../../stores/settingsStore";
|
import { useSettingsStore } from "../../../stores/settingsStore";
|
||||||
import type { PostProcessProvider } from "../../../lib/types";
|
import type { PostProcessProvider } from "@/bindings";
|
||||||
import type { ModelOption } from "./types";
|
import type { ModelOption } from "./types";
|
||||||
import type { DropdownOption } from "../../ui/Dropdown";
|
import type { DropdownOption } from "../../ui/Dropdown";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import { Dropdown } from "../ui/Dropdown";
|
import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import { RecordingRetentionPeriod } from "../../lib/types";
|
import { RecordingRetentionPeriod } from "@/bindings";
|
||||||
|
|
||||||
interface RecordingRetentionPeriodProps {
|
interface RecordingRetentionPeriodProps {
|
||||||
descriptionMode?: "inline" | "tooltip";
|
descriptionMode?: "inline" | "tooltip";
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import { Dropdown } from "../ui/Dropdown";
|
import { Dropdown } from "../ui/Dropdown";
|
||||||
import { SettingContainer } from "../ui/SettingContainer";
|
import { SettingContainer } from "../ui/SettingContainer";
|
||||||
import { useSettings } from "../../hooks/useSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import type { OverlayPosition } from "../../lib/types";
|
import type { OverlayPosition } from "@/bindings";
|
||||||
|
|
||||||
interface ShowOverlayProps {
|
interface ShowOverlayProps {
|
||||||
descriptionMode?: "inline" | "tooltip";
|
descriptionMode?: "inline" | "tooltip";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "@/bindings";
|
||||||
import { SettingContainer } from "../../ui/SettingContainer";
|
import { SettingContainer } from "../../ui/SettingContainer";
|
||||||
import { Button } from "../../ui/Button";
|
import { Button } from "../../ui/Button";
|
||||||
|
|
||||||
|
|
@ -19,8 +19,12 @@ export const LogDirectory: React.FC<LogDirectoryProps> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadLogDirectory = async () => {
|
const loadLogDirectory = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await invoke<string>("get_log_dir_path");
|
const result = await commands.getLogDirPath();
|
||||||
setLogDir(result);
|
if (result.status === "ok") {
|
||||||
|
setLogDir(result.data);
|
||||||
|
} else {
|
||||||
|
setError(result.error);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err && typeof err === "object" && "message" in err
|
err && typeof err === "object" && "message" in err
|
||||||
|
|
@ -38,7 +42,7 @@ export const LogDirectory: React.FC<LogDirectoryProps> = ({
|
||||||
const handleOpen = async () => {
|
const handleOpen = async () => {
|
||||||
if (!logDir) return;
|
if (!logDir) return;
|
||||||
try {
|
try {
|
||||||
await invoke("open_log_dir");
|
await commands.openLogDir();
|
||||||
} catch (openError) {
|
} catch (openError) {
|
||||||
console.error("Failed to open log directory:", openError);
|
console.error("Failed to open log directory:", openError);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ import React from "react";
|
||||||
import { SettingContainer } from "../../ui/SettingContainer";
|
import { SettingContainer } from "../../ui/SettingContainer";
|
||||||
import { Dropdown, type DropdownOption } from "../../ui/Dropdown";
|
import { Dropdown, type DropdownOption } from "../../ui/Dropdown";
|
||||||
import { useSettings } from "../../../hooks/useSettings";
|
import { useSettings } from "../../../hooks/useSettings";
|
||||||
|
import type { LogLevel } from "../../../bindings";
|
||||||
|
|
||||||
const LOG_LEVEL_OPTIONS: DropdownOption[] = [
|
const LOG_LEVEL_OPTIONS: DropdownOption[] = [
|
||||||
{ value: "5", label: "Error" },
|
{ value: "error", label: "Error" },
|
||||||
{ value: "4", label: "Warn" },
|
{ value: "warn", label: "Warn" },
|
||||||
{ value: "3", label: "Info" },
|
{ value: "info", label: "Info" },
|
||||||
{ value: "2", label: "Debug" },
|
{ value: "debug", label: "Debug" },
|
||||||
{ value: "1", label: "Trace" },
|
{ value: "trace", label: "Trace" },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface LogLevelSelectorProps {
|
interface LogLevelSelectorProps {
|
||||||
|
|
@ -21,19 +22,13 @@ export const LogLevelSelector: React.FC<LogLevelSelectorProps> = ({
|
||||||
grouped = false,
|
grouped = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { settings, updateSetting, isUpdating } = useSettings();
|
const { settings, updateSetting, isUpdating } = useSettings();
|
||||||
const currentLevel = settings?.log_level ?? 2;
|
const currentLevel = settings?.log_level ?? "debug";
|
||||||
const isLevelUpdating = isUpdating("log_level");
|
|
||||||
|
|
||||||
const selectedValue = currentLevel.toString();
|
|
||||||
|
|
||||||
const handleSelect = async (value: string) => {
|
const handleSelect = async (value: string) => {
|
||||||
const parsed = Number.parseInt(value, 10);
|
if (value === currentLevel) return;
|
||||||
if (Number.isNaN(parsed) || parsed === currentLevel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateSetting("log_level", parsed as typeof currentLevel);
|
await updateSetting("log_level", value as LogLevel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update log level:", error);
|
console.error("Failed to update log level:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -47,14 +42,12 @@ export const LogLevelSelector: React.FC<LogLevelSelectorProps> = ({
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
>
|
>
|
||||||
<div className="space-y-1">
|
<Dropdown
|
||||||
<Dropdown
|
options={LOG_LEVEL_OPTIONS}
|
||||||
options={LOG_LEVEL_OPTIONS}
|
selectedValue={currentLevel}
|
||||||
selectedValue={selectedValue}
|
onSelect={handleSelect}
|
||||||
onSelect={handleSelect}
|
disabled={!settings || isUpdating("log_level")}
|
||||||
disabled={!settings || isLevelUpdating}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,9 @@ import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { AudioPlayer } from "../../ui/AudioPlayer";
|
import { AudioPlayer } from "../../ui/AudioPlayer";
|
||||||
import { Button } from "../../ui/Button";
|
import { Button } from "../../ui/Button";
|
||||||
import { Copy, Star, Check, Trash2, FolderOpen } from "lucide-react";
|
import { Copy, Star, Check, Trash2, FolderOpen } from "lucide-react";
|
||||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { commands, type HistoryEntry } from "@/bindings";
|
||||||
interface HistoryEntry {
|
|
||||||
id: number;
|
|
||||||
file_name: string;
|
|
||||||
timestamp: number;
|
|
||||||
saved: boolean;
|
|
||||||
title: string;
|
|
||||||
transcription_text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OpenRecordingsButtonProps {
|
interface OpenRecordingsButtonProps {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
|
@ -39,8 +31,10 @@ export const HistorySettings: React.FC = () => {
|
||||||
|
|
||||||
const loadHistoryEntries = useCallback(async () => {
|
const loadHistoryEntries = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const entries = await invoke<HistoryEntry[]>("get_history_entries");
|
const result = await commands.getHistoryEntries();
|
||||||
setHistoryEntries(entries);
|
if (result.status === "ok") {
|
||||||
|
setHistoryEntries(result.data);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load history entries:", error);
|
console.error("Failed to load history entries:", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -73,9 +67,9 @@ export const HistorySettings: React.FC = () => {
|
||||||
};
|
};
|
||||||
}, [loadHistoryEntries]);
|
}, [loadHistoryEntries]);
|
||||||
|
|
||||||
const toggleSaved = async (id: number) => {
|
const toggleSaved = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
await invoke("toggle_history_entry_saved", { id });
|
await commands.toggleHistoryEntrySaved(id);
|
||||||
// No need to reload here - the event listener will handle it
|
// No need to reload here - the event listener will handle it
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to toggle saved status:", error);
|
console.error("Failed to toggle saved status:", error);
|
||||||
|
|
@ -92,20 +86,20 @@ export const HistorySettings: React.FC = () => {
|
||||||
|
|
||||||
const getAudioUrl = async (fileName: string) => {
|
const getAudioUrl = async (fileName: string) => {
|
||||||
try {
|
try {
|
||||||
const filePath = await invoke<string>("get_audio_file_path", {
|
const result = await commands.getAudioFilePath(fileName);
|
||||||
fileName,
|
if (result.status === "ok") {
|
||||||
});
|
return convertFileSrc(`${result.data}`, "asset");
|
||||||
|
}
|
||||||
return convertFileSrc(`${filePath}`, "asset");
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get audio file path:", error);
|
console.error("Failed to get audio file path:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteAudioEntry = async (id: number) => {
|
const deleteAudioEntry = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
await invoke("delete_history_entry", { id });
|
await commands.deleteHistoryEntry(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete audio entry:", error);
|
console.error("Failed to delete audio entry:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -114,7 +108,7 @@ export const HistorySettings: React.FC = () => {
|
||||||
|
|
||||||
const openRecordingsFolder = async () => {
|
const openRecordingsFolder = async () => {
|
||||||
try {
|
try {
|
||||||
await invoke("open_recordings_folder");
|
await commands.openRecordingsFolder();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to open recordings folder:", error);
|
console.error("Failed to open recordings folder:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +193,7 @@ interface HistoryEntryProps {
|
||||||
onToggleSaved: () => void;
|
onToggleSaved: () => void;
|
||||||
onCopyText: () => void;
|
onCopyText: () => void;
|
||||||
getAudioUrl: (fileName: string) => Promise<string | null>;
|
getAudioUrl: (fileName: string) => Promise<string | null>;
|
||||||
deleteAudio: (id: number) => Promise<void>;
|
deleteAudio: (id: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HistoryEntryComponent: React.FC<HistoryEntryProps> = ({
|
const HistoryEntryComponent: React.FC<HistoryEntryProps> = ({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { RefreshCcw } from "lucide-react";
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import { commands } from "@/bindings";
|
||||||
|
|
||||||
import { SettingsGroup } from "../../ui/SettingsGroup";
|
import { SettingsGroup } from "../../ui/SettingsGroup";
|
||||||
import { SettingContainer } from "../../ui/SettingContainer";
|
import { SettingContainer } from "../../ui/SettingContainer";
|
||||||
|
|
@ -16,7 +16,7 @@ import { ApiKeyField } from "../PostProcessingSettingsApi/ApiKeyField";
|
||||||
import { ModelSelect } from "../PostProcessingSettingsApi/ModelSelect";
|
import { ModelSelect } from "../PostProcessingSettingsApi/ModelSelect";
|
||||||
import { usePostProcessProviderState } from "../PostProcessingSettingsApi/usePostProcessProviderState";
|
import { usePostProcessProviderState } from "../PostProcessingSettingsApi/usePostProcessProviderState";
|
||||||
import { useSettings } from "../../../hooks/useSettings";
|
import { useSettings } from "../../../hooks/useSettings";
|
||||||
import type { LLMPrompt } from "../../../lib/types";
|
import type { LLMPrompt } from "@/bindings";
|
||||||
|
|
||||||
const DisabledNotice: React.FC<{ children: React.ReactNode }> = ({
|
const DisabledNotice: React.FC<{ children: React.ReactNode }> = ({
|
||||||
children,
|
children,
|
||||||
|
|
@ -178,13 +178,15 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
|
||||||
if (!draftName.trim() || !draftText.trim()) return;
|
if (!draftName.trim() || !draftText.trim()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newPrompt = await invoke<LLMPrompt>("add_post_process_prompt", {
|
const result = await commands.addPostProcessPrompt(
|
||||||
name: draftName.trim(),
|
draftName.trim(),
|
||||||
prompt: draftText.trim(),
|
draftText.trim()
|
||||||
});
|
);
|
||||||
await refreshSettings();
|
if (result.status === "ok") {
|
||||||
updateSetting("post_process_selected_prompt_id", newPrompt.id);
|
await refreshSettings();
|
||||||
setIsCreating(false);
|
updateSetting("post_process_selected_prompt_id", result.data.id);
|
||||||
|
setIsCreating(false);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create prompt:", error);
|
console.error("Failed to create prompt:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -194,11 +196,11 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
|
||||||
if (!selectedPromptId || !draftName.trim() || !draftText.trim()) return;
|
if (!selectedPromptId || !draftName.trim() || !draftText.trim()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("update_post_process_prompt", {
|
await commands.updatePostProcessPrompt(
|
||||||
id: selectedPromptId,
|
selectedPromptId,
|
||||||
name: draftName.trim(),
|
draftName.trim(),
|
||||||
prompt: draftText.trim(),
|
draftText.trim()
|
||||||
});
|
);
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update prompt:", error);
|
console.error("Failed to update prompt:", error);
|
||||||
|
|
@ -209,7 +211,7 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
|
||||||
if (!promptId) return;
|
if (!promptId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("delete_post_process_prompt", { id: promptId });
|
await commands.deletePostProcessPrompt(promptId);
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,6 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { commands, type ModelInfo } from "@/bindings";
|
||||||
interface ModelInfo {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
filename: string;
|
|
||||||
url?: string;
|
|
||||||
size_mb: number;
|
|
||||||
is_downloaded: boolean;
|
|
||||||
is_downloading: boolean;
|
|
||||||
partial_size: number;
|
|
||||||
is_directory: boolean;
|
|
||||||
accuracy_score: number;
|
|
||||||
speed_score: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DownloadProgress {
|
interface DownloadProgress {
|
||||||
model_id: string;
|
model_id: string;
|
||||||
|
|
@ -43,9 +28,13 @@ export const useModels = () => {
|
||||||
|
|
||||||
const loadModels = async () => {
|
const loadModels = async () => {
|
||||||
try {
|
try {
|
||||||
const modelList = await invoke<ModelInfo[]>("get_available_models");
|
const result = await commands.getAvailableModels();
|
||||||
setModels(modelList);
|
if (result.status === "ok") {
|
||||||
setError(null);
|
setModels(result.data);
|
||||||
|
setError(null);
|
||||||
|
} else {
|
||||||
|
setError(`Failed to load models: ${result.error}`);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`Failed to load models: ${err}`);
|
setError(`Failed to load models: ${err}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -55,8 +44,10 @@ export const useModels = () => {
|
||||||
|
|
||||||
const loadCurrentModel = async () => {
|
const loadCurrentModel = async () => {
|
||||||
try {
|
try {
|
||||||
const current = await invoke<string>("get_current_model");
|
const result = await commands.getCurrentModel();
|
||||||
setCurrentModel(current);
|
if (result.status === "ok") {
|
||||||
|
setCurrentModel(result.data);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load current model:", err);
|
console.error("Failed to load current model:", err);
|
||||||
}
|
}
|
||||||
|
|
@ -64,10 +55,14 @@ export const useModels = () => {
|
||||||
|
|
||||||
const checkFirstRun = async () => {
|
const checkFirstRun = async () => {
|
||||||
try {
|
try {
|
||||||
const hasModels = await invoke<boolean>("has_any_models_available");
|
const result = await commands.hasAnyModelsAvailable();
|
||||||
setHasAnyModels(hasModels);
|
if (result.status === "ok") {
|
||||||
setIsFirstRun(!hasModels);
|
const hasModels = result.data;
|
||||||
return !hasModels;
|
setHasAnyModels(hasModels);
|
||||||
|
setIsFirstRun(!hasModels);
|
||||||
|
return !hasModels;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to check model availability:", err);
|
console.error("Failed to check model availability:", err);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -77,11 +72,16 @@ export const useModels = () => {
|
||||||
const selectModel = async (modelId: string) => {
|
const selectModel = async (modelId: string) => {
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
await invoke("set_active_model", { modelId });
|
const result = await commands.setActiveModel(modelId);
|
||||||
setCurrentModel(modelId);
|
if (result.status === "ok") {
|
||||||
setIsFirstRun(false);
|
setCurrentModel(modelId);
|
||||||
setHasAnyModels(true);
|
setIsFirstRun(false);
|
||||||
return true;
|
setHasAnyModels(true);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
setError(`Failed to switch to model: ${result.error}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`Failed to switch to model: ${err}`);
|
setError(`Failed to switch to model: ${err}`);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -92,8 +92,18 @@ export const useModels = () => {
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
setDownloadingModels((prev) => new Set(prev.add(modelId)));
|
setDownloadingModels((prev) => new Set(prev.add(modelId)));
|
||||||
await invoke("download_model", { modelId });
|
const result = await commands.downloadModel(modelId);
|
||||||
return true;
|
if (result.status === "ok") {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
setError(`Failed to download model: ${result.error}`);
|
||||||
|
setDownloadingModels((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(modelId);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`Failed to download model: ${err}`);
|
setError(`Failed to download model: ${err}`);
|
||||||
setDownloadingModels((prev) => {
|
setDownloadingModels((prev) => {
|
||||||
|
|
@ -108,9 +118,14 @@ export const useModels = () => {
|
||||||
const deleteModel = async (modelId: string) => {
|
const deleteModel = async (modelId: string) => {
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
await invoke("delete_model", { modelId });
|
const result = await commands.deleteModel(modelId);
|
||||||
await loadModels(); // Refresh the list
|
if (result.status === "ok") {
|
||||||
return true;
|
await loadModels(); // Refresh the list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
setError(`Failed to delete model: ${result.error}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`Failed to delete model: ${err}`);
|
setError(`Failed to delete model: ${err}`);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useSettingsStore } from "../stores/settingsStore";
|
import { useSettingsStore } from "../stores/settingsStore";
|
||||||
import { Settings, AudioDevice } from "../lib/types";
|
import type { AppSettings as Settings, AudioDevice } from "@/bindings";
|
||||||
|
|
||||||
interface UseSettingsReturn {
|
interface UseSettingsReturn {
|
||||||
// State
|
// State
|
||||||
|
|
|
||||||
151
src/lib/types.ts
151
src/lib/types.ts
|
|
@ -1,151 +0,0 @@
|
||||||
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 ShortcutBindingsMapSchema = z.record(
|
|
||||||
z.string(),
|
|
||||||
ShortcutBindingSchema,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const AudioDeviceSchema = z.object({
|
|
||||||
index: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
is_default: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const OverlayPositionSchema = z.enum(["none", "top", "bottom"]);
|
|
||||||
export type OverlayPosition = z.infer<typeof OverlayPositionSchema>;
|
|
||||||
|
|
||||||
export const ModelUnloadTimeoutSchema = z.enum([
|
|
||||||
"never",
|
|
||||||
"immediately",
|
|
||||||
"min2",
|
|
||||||
"min5",
|
|
||||||
"min10",
|
|
||||||
"min15",
|
|
||||||
"hour1",
|
|
||||||
"sec5",
|
|
||||||
]);
|
|
||||||
export type ModelUnloadTimeout = z.infer<typeof ModelUnloadTimeoutSchema>;
|
|
||||||
|
|
||||||
export const PasteMethodSchema = z.enum(["ctrl_v", "direct", "shift_insert"]);
|
|
||||||
export type PasteMethod = z.infer<typeof PasteMethodSchema>;
|
|
||||||
|
|
||||||
export const ClipboardHandlingSchema = z.enum([
|
|
||||||
"dont_modify",
|
|
||||||
"copy_to_clipboard",
|
|
||||||
]);
|
|
||||||
export type ClipboardHandling = z.infer<typeof ClipboardHandlingSchema>;
|
|
||||||
|
|
||||||
export const LogLevelSchema = z.number().int().min(1).max(5).default(2);
|
|
||||||
export type LogLevelValue = z.infer<typeof LogLevelSchema>;
|
|
||||||
|
|
||||||
export const RecordingRetentionPeriodSchema = z.enum([
|
|
||||||
"never",
|
|
||||||
"preserve_limit",
|
|
||||||
"days3",
|
|
||||||
"weeks2",
|
|
||||||
"months3",
|
|
||||||
]);
|
|
||||||
export type RecordingRetentionPeriod = z.infer<
|
|
||||||
typeof RecordingRetentionPeriodSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const LLMPromptSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
prompt: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type LLMPrompt = z.infer<typeof LLMPromptSchema>;
|
|
||||||
|
|
||||||
export const PostProcessProviderSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
label: z.string(),
|
|
||||||
base_url: z.string(),
|
|
||||||
allow_base_url_edit: z.boolean().optional().default(false),
|
|
||||||
models_endpoint: z.string().nullable().optional(),
|
|
||||||
kind: z
|
|
||||||
.enum(["openai_compatible", "anthropic"])
|
|
||||||
.optional()
|
|
||||||
.default("openai_compatible"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type PostProcessProvider = z.infer<typeof PostProcessProviderSchema>;
|
|
||||||
|
|
||||||
export const SettingsSchema = z.object({
|
|
||||||
bindings: ShortcutBindingsMapSchema,
|
|
||||||
push_to_talk: z.boolean(),
|
|
||||||
audio_feedback: z.boolean(),
|
|
||||||
audio_feedback_volume: z.number().optional().default(1.0),
|
|
||||||
sound_theme: z
|
|
||||||
.enum(["marimba", "pop", "custom"])
|
|
||||||
.optional()
|
|
||||||
.default("marimba"),
|
|
||||||
start_hidden: z.boolean().optional().default(false),
|
|
||||||
autostart_enabled: z.boolean().optional().default(false),
|
|
||||||
selected_model: z.string(),
|
|
||||||
always_on_microphone: z.boolean(),
|
|
||||||
selected_microphone: z.string().nullable().optional(),
|
|
||||||
clamshell_microphone: z.string().nullable().optional(),
|
|
||||||
selected_output_device: z.string().nullable().optional(),
|
|
||||||
translate_to_english: z.boolean(),
|
|
||||||
selected_language: z.string(),
|
|
||||||
overlay_position: OverlayPositionSchema,
|
|
||||||
debug_mode: z.boolean(),
|
|
||||||
log_level: LogLevelSchema.optional().default(2),
|
|
||||||
custom_words: z.array(z.string()).optional().default([]),
|
|
||||||
model_unload_timeout: ModelUnloadTimeoutSchema.optional().default("never"),
|
|
||||||
word_correction_threshold: z.number().optional().default(0.18),
|
|
||||||
history_limit: z.number().optional().default(5),
|
|
||||||
recording_retention_period:
|
|
||||||
RecordingRetentionPeriodSchema.optional().default("preserve_limit"),
|
|
||||||
paste_method: PasteMethodSchema.optional().default("ctrl_v"),
|
|
||||||
clipboard_handling: ClipboardHandlingSchema.optional().default("dont_modify"),
|
|
||||||
post_process_enabled: z.boolean().optional().default(false),
|
|
||||||
post_process_provider_id: z.string().optional().default("openai"),
|
|
||||||
post_process_providers: z
|
|
||||||
.array(PostProcessProviderSchema)
|
|
||||||
.optional()
|
|
||||||
.default([]),
|
|
||||||
post_process_api_keys: z.record(z.string()).optional().default({}),
|
|
||||||
post_process_models: z.record(z.string()).optional().default({}),
|
|
||||||
post_process_prompts: z.array(LLMPromptSchema).optional().default([]),
|
|
||||||
post_process_selected_prompt_id: z.string().nullable().optional(),
|
|
||||||
mute_while_recording: z.boolean().optional().default(false),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const BindingResponseSchema = z.object({
|
|
||||||
success: z.boolean(),
|
|
||||||
binding: ShortcutBindingSchema.nullable(),
|
|
||||||
error: z.string().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AudioDevice = z.infer<typeof AudioDeviceSchema>;
|
|
||||||
export type BindingResponse = z.infer<typeof BindingResponseSchema>;
|
|
||||||
export type ShortcutBinding = z.infer<typeof ShortcutBindingSchema>;
|
|
||||||
export type ShortcutBindingsMap = z.infer<typeof ShortcutBindingsMapSchema>;
|
|
||||||
export type Settings = z.infer<typeof SettingsSchema>;
|
|
||||||
|
|
||||||
export const ModelInfoSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
filename: z.string(),
|
|
||||||
url: z.string().optional(),
|
|
||||||
size_mb: z.number(),
|
|
||||||
is_downloaded: z.boolean(),
|
|
||||||
is_downloading: z.boolean(),
|
|
||||||
partial_size: z.number(),
|
|
||||||
is_directory: z.boolean(),
|
|
||||||
accuracy_score: z.number(),
|
|
||||||
speed_score: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ModelInfo = z.infer<typeof ModelInfoSchema>;
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
|
@ -7,6 +6,7 @@ import {
|
||||||
CancelIcon,
|
CancelIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import "./RecordingOverlay.css";
|
import "./RecordingOverlay.css";
|
||||||
|
import { commands } from "@/bindings";
|
||||||
|
|
||||||
type OverlayState = "recording" | "transcribing";
|
type OverlayState = "recording" | "transcribing";
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ const RecordingOverlay: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className="cancel-button"
|
className="cancel-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
invoke("cancel_operation");
|
commands.cancelOperation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CancelIcon />
|
<CancelIcon />
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
import { subscribeWithSelector } from "zustand/middleware";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import type { AppSettings as Settings, AudioDevice } from "@/bindings";
|
||||||
import { Settings, AudioDevice } from "../lib/types";
|
import { commands } from "@/bindings";
|
||||||
|
|
||||||
interface SettingsStore {
|
interface SettingsStore {
|
||||||
settings: Settings | null;
|
settings: Settings | null;
|
||||||
|
defaultSettings: Settings | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isUpdating: Record<string, boolean>;
|
isUpdating: Record<string, boolean>;
|
||||||
audioDevices: AudioDevice[];
|
audioDevices: AudioDevice[];
|
||||||
|
|
@ -14,6 +15,7 @@ interface SettingsStore {
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
initialize: () => Promise<void>;
|
initialize: () => Promise<void>;
|
||||||
|
loadDefaultSettings: () => Promise<void>;
|
||||||
updateSetting: <K extends keyof Settings>(
|
updateSetting: <K extends keyof Settings>(
|
||||||
key: K,
|
key: K,
|
||||||
value: Settings[K],
|
value: Settings[K],
|
||||||
|
|
@ -48,6 +50,7 @@ interface SettingsStore {
|
||||||
|
|
||||||
// Internal state setters
|
// Internal state setters
|
||||||
setSettings: (settings: Settings | null) => void;
|
setSettings: (settings: Settings | null) => void;
|
||||||
|
setDefaultSettings: (defaultSettings: Settings | null) => void;
|
||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
setUpdating: (key: string, updating: boolean) => void;
|
setUpdating: (key: string, updating: boolean) => void;
|
||||||
setAudioDevices: (devices: AudioDevice[]) => void;
|
setAudioDevices: (devices: AudioDevice[]) => void;
|
||||||
|
|
@ -55,29 +58,8 @@ interface SettingsStore {
|
||||||
setCustomSounds: (sounds: { start: boolean; stop: boolean }) => void;
|
setCustomSounds: (sounds: { start: boolean; stop: boolean }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Default post-processing settings are now managed in Rust
|
// Note: Default settings are now fetched from Rust via commands.getDefaultSettings()
|
||||||
// Settings will always have providers after migration
|
// This ensures platform-specific defaults (like overlay_position, shortcuts, paste_method) work correctly
|
||||||
const DEFAULT_SETTINGS: Partial<Settings> = {
|
|
||||||
always_on_microphone: false,
|
|
||||||
audio_feedback: true,
|
|
||||||
audio_feedback_volume: 1.0,
|
|
||||||
sound_theme: "marimba",
|
|
||||||
start_hidden: false,
|
|
||||||
autostart_enabled: false,
|
|
||||||
push_to_talk: false,
|
|
||||||
selected_microphone: "Default",
|
|
||||||
clamshell_microphone: "Default",
|
|
||||||
selected_output_device: "Default",
|
|
||||||
translate_to_english: false,
|
|
||||||
selected_language: "auto",
|
|
||||||
overlay_position: "bottom",
|
|
||||||
debug_mode: false,
|
|
||||||
log_level: 2,
|
|
||||||
custom_words: [],
|
|
||||||
history_limit: 5,
|
|
||||||
recording_retention_period: "preserve_limit",
|
|
||||||
mute_while_recording: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_AUDIO_DEVICE: AudioDevice = {
|
const DEFAULT_AUDIO_DEVICE: AudioDevice = {
|
||||||
index: "default",
|
index: "default",
|
||||||
|
|
@ -89,60 +71,61 @@ const settingUpdaters: {
|
||||||
[K in keyof Settings]?: (value: Settings[K]) => Promise<unknown>;
|
[K in keyof Settings]?: (value: Settings[K]) => Promise<unknown>;
|
||||||
} = {
|
} = {
|
||||||
always_on_microphone: (value) =>
|
always_on_microphone: (value) =>
|
||||||
invoke("update_microphone_mode", { alwaysOn: value }),
|
commands.updateMicrophoneMode(value as boolean),
|
||||||
audio_feedback: (value) =>
|
audio_feedback: (value) =>
|
||||||
invoke("change_audio_feedback_setting", { enabled: value }),
|
commands.changeAudioFeedbackSetting(value as boolean),
|
||||||
audio_feedback_volume: (value) =>
|
audio_feedback_volume: (value) =>
|
||||||
invoke("change_audio_feedback_volume_setting", { volume: value }),
|
commands.changeAudioFeedbackVolumeSetting(value as number),
|
||||||
sound_theme: (value) =>
|
sound_theme: (value) =>
|
||||||
invoke("change_sound_theme_setting", { theme: value }),
|
commands.changeSoundThemeSetting(value as string),
|
||||||
start_hidden: (value) =>
|
start_hidden: (value) =>
|
||||||
invoke("change_start_hidden_setting", { enabled: value }),
|
commands.changeStartHiddenSetting(value as boolean),
|
||||||
autostart_enabled: (value) =>
|
autostart_enabled: (value) =>
|
||||||
invoke("change_autostart_setting", { enabled: value }),
|
commands.changeAutostartSetting(value as boolean),
|
||||||
push_to_talk: (value) => invoke("change_ptt_setting", { enabled: value }),
|
push_to_talk: (value) => commands.changePttSetting(value as boolean),
|
||||||
selected_microphone: (value) =>
|
selected_microphone: (value) =>
|
||||||
invoke("set_selected_microphone", {
|
commands.setSelectedMicrophone(
|
||||||
deviceName: value === "Default" ? "default" : value,
|
(value as string) === "Default" || value === null ? "default" : (value as string)
|
||||||
}),
|
),
|
||||||
clamshell_microphone: (value) =>
|
clamshell_microphone: (value) =>
|
||||||
invoke("set_clamshell_microphone", {
|
commands.setClamshellMicrophone(
|
||||||
deviceName: value === "Default" ? "default" : value,
|
(value as string) === "Default" ? "default" : (value as string)
|
||||||
}),
|
),
|
||||||
selected_output_device: (value) =>
|
selected_output_device: (value) =>
|
||||||
invoke("set_selected_output_device", {
|
commands.setSelectedOutputDevice(
|
||||||
deviceName: value === "Default" ? "default" : value,
|
(value as string) === "Default" || value === null ? "default" : (value as string)
|
||||||
}),
|
),
|
||||||
recording_retention_period: (value) =>
|
recording_retention_period: (value) =>
|
||||||
invoke("update_recording_retention_period", { period: value }),
|
commands.updateRecordingRetentionPeriod(value as string),
|
||||||
translate_to_english: (value) =>
|
translate_to_english: (value) =>
|
||||||
invoke("change_translate_to_english_setting", { enabled: value }),
|
commands.changeTranslateToEnglishSetting(value as boolean),
|
||||||
selected_language: (value) =>
|
selected_language: (value) =>
|
||||||
invoke("change_selected_language_setting", { language: value }),
|
commands.changeSelectedLanguageSetting(value as string),
|
||||||
overlay_position: (value) =>
|
overlay_position: (value) =>
|
||||||
invoke("change_overlay_position_setting", { position: value }),
|
commands.changeOverlayPositionSetting(value as string),
|
||||||
debug_mode: (value) =>
|
debug_mode: (value) =>
|
||||||
invoke("change_debug_mode_setting", { enabled: value }),
|
commands.changeDebugModeSetting(value as boolean),
|
||||||
custom_words: (value) => invoke("update_custom_words", { words: value }),
|
custom_words: (value) => commands.updateCustomWords(value as string[]),
|
||||||
word_correction_threshold: (value) =>
|
word_correction_threshold: (value) =>
|
||||||
invoke("change_word_correction_threshold_setting", { threshold: value }),
|
commands.changeWordCorrectionThresholdSetting(value as number),
|
||||||
paste_method: (value) =>
|
paste_method: (value) =>
|
||||||
invoke("change_paste_method_setting", { method: value }),
|
commands.changePasteMethodSetting(value as string),
|
||||||
clipboard_handling: (value) =>
|
clipboard_handling: (value) =>
|
||||||
invoke("change_clipboard_handling_setting", { handling: value }),
|
commands.changeClipboardHandlingSetting(value as string),
|
||||||
history_limit: (value) => invoke("update_history_limit", { limit: value }),
|
history_limit: (value) => commands.updateHistoryLimit(value as string),
|
||||||
post_process_enabled: (value) =>
|
post_process_enabled: (value) =>
|
||||||
invoke("change_post_process_enabled_setting", { enabled: value }),
|
commands.changePostProcessEnabledSetting(value as boolean),
|
||||||
post_process_selected_prompt_id: (value) =>
|
post_process_selected_prompt_id: (value) =>
|
||||||
invoke("set_post_process_selected_prompt", { id: value }),
|
commands.setPostProcessSelectedPrompt(value as string),
|
||||||
mute_while_recording: (value) =>
|
mute_while_recording: (value) =>
|
||||||
invoke("change_mute_while_recording_setting", { enabled: value }),
|
commands.changeMuteWhileRecordingSetting(value as boolean),
|
||||||
log_level: (value) => invoke("set_log_level", { level: value }),
|
log_level: (value) => commands.setLogLevel(value as any),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSettingsStore = create<SettingsStore>()(
|
export const useSettingsStore = create<SettingsStore>()(
|
||||||
subscribeWithSelector((set, get) => ({
|
subscribeWithSelector((set, get) => ({
|
||||||
settings: null,
|
settings: null,
|
||||||
|
defaultSettings: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isUpdating: {},
|
isUpdating: {},
|
||||||
audioDevices: [],
|
audioDevices: [],
|
||||||
|
|
@ -152,6 +135,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
|
|
||||||
// Internal setters
|
// Internal setters
|
||||||
setSettings: (settings) => set({ settings }),
|
setSettings: (settings) => set({ settings }),
|
||||||
|
setDefaultSettings: (defaultSettings) => set({ defaultSettings }),
|
||||||
setLoading: (isLoading) => set({ isLoading }),
|
setLoading: (isLoading) => set({ isLoading }),
|
||||||
setUpdating: (key, updating) =>
|
setUpdating: (key, updating) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
|
|
@ -168,48 +152,24 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
// Load settings from store
|
// Load settings from store
|
||||||
refreshSettings: async () => {
|
refreshSettings: async () => {
|
||||||
try {
|
try {
|
||||||
const { load } = await import("@tauri-apps/plugin-store");
|
const result = await commands.getAppSettings();
|
||||||
const store = await load("settings_store.json", {
|
if (result.status === "ok") {
|
||||||
defaults: DEFAULT_SETTINGS,
|
const settings = result.data;
|
||||||
autoSave: false,
|
const normalizedSettings: Settings = {
|
||||||
});
|
...settings,
|
||||||
const settings = (await store.get("settings")) as Settings;
|
always_on_microphone: settings.always_on_microphone ?? false,
|
||||||
|
selected_microphone:
|
||||||
// Load additional settings that come from invoke calls
|
settings.selected_microphone ?? "Default",
|
||||||
const [
|
clamshell_microphone:
|
||||||
microphoneMode,
|
settings.clamshell_microphone ?? "Default",
|
||||||
selectedMicrophone,
|
selected_output_device:
|
||||||
clamshellMicrophone,
|
settings.selected_output_device ?? "Default",
|
||||||
selectedOutputDevice,
|
};
|
||||||
] = await Promise.allSettled([
|
set({ settings: normalizedSettings, isLoading: false });
|
||||||
invoke("get_microphone_mode"),
|
} else {
|
||||||
invoke("get_selected_microphone"),
|
console.error("Failed to load settings:", result.error);
|
||||||
invoke("get_clamshell_microphone"),
|
set({ isLoading: false });
|
||||||
invoke("get_selected_output_device"),
|
}
|
||||||
]);
|
|
||||||
|
|
||||||
// Merge all settings
|
|
||||||
const mergedSettings: Settings = {
|
|
||||||
...settings,
|
|
||||||
always_on_microphone:
|
|
||||||
microphoneMode.status === "fulfilled"
|
|
||||||
? (microphoneMode.value as boolean)
|
|
||||||
: false,
|
|
||||||
selected_microphone:
|
|
||||||
selectedMicrophone.status === "fulfilled"
|
|
||||||
? (selectedMicrophone.value as string)
|
|
||||||
: "Default",
|
|
||||||
clamshell_microphone:
|
|
||||||
clamshellMicrophone.status === "fulfilled"
|
|
||||||
? (clamshellMicrophone.value as string)
|
|
||||||
: "Default",
|
|
||||||
selected_output_device:
|
|
||||||
selectedOutputDevice.status === "fulfilled"
|
|
||||||
? (selectedOutputDevice.value as string)
|
|
||||||
: "Default",
|
|
||||||
};
|
|
||||||
|
|
||||||
set({ settings: mergedSettings, isLoading: false });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load settings:", error);
|
console.error("Failed to load settings:", error);
|
||||||
set({ isLoading: false });
|
set({ isLoading: false });
|
||||||
|
|
@ -219,16 +179,18 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
// Load audio devices
|
// Load audio devices
|
||||||
refreshAudioDevices: async () => {
|
refreshAudioDevices: async () => {
|
||||||
try {
|
try {
|
||||||
const devices: AudioDevice[] = await invoke(
|
const result = await commands.getAvailableMicrophones();
|
||||||
"get_available_microphones",
|
if (result.status === "ok") {
|
||||||
);
|
const devicesWithDefault = [
|
||||||
const devicesWithDefault = [
|
DEFAULT_AUDIO_DEVICE,
|
||||||
DEFAULT_AUDIO_DEVICE,
|
...result.data.filter(
|
||||||
...devices.filter(
|
(d) => d.name !== "Default" && d.name !== "default",
|
||||||
(d) => d.name !== "Default" && d.name !== "default",
|
),
|
||||||
),
|
];
|
||||||
];
|
set({ audioDevices: devicesWithDefault });
|
||||||
set({ audioDevices: devicesWithDefault });
|
} else {
|
||||||
|
set({ audioDevices: [DEFAULT_AUDIO_DEVICE] });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load audio devices:", error);
|
console.error("Failed to load audio devices:", error);
|
||||||
set({ audioDevices: [DEFAULT_AUDIO_DEVICE] });
|
set({ audioDevices: [DEFAULT_AUDIO_DEVICE] });
|
||||||
|
|
@ -238,16 +200,18 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
// Load output devices
|
// Load output devices
|
||||||
refreshOutputDevices: async () => {
|
refreshOutputDevices: async () => {
|
||||||
try {
|
try {
|
||||||
const devices: AudioDevice[] = await invoke(
|
const result = await commands.getAvailableOutputDevices();
|
||||||
"get_available_output_devices",
|
if (result.status === "ok") {
|
||||||
);
|
const devicesWithDefault = [
|
||||||
const devicesWithDefault = [
|
DEFAULT_AUDIO_DEVICE,
|
||||||
DEFAULT_AUDIO_DEVICE,
|
...result.data.filter(
|
||||||
...devices.filter(
|
(d) => d.name !== "Default" && d.name !== "default",
|
||||||
(d) => d.name !== "Default" && d.name !== "default",
|
),
|
||||||
),
|
];
|
||||||
];
|
set({ outputDevices: devicesWithDefault });
|
||||||
set({ outputDevices: devicesWithDefault });
|
} else {
|
||||||
|
set({ outputDevices: [DEFAULT_AUDIO_DEVICE] });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load output devices:", error);
|
console.error("Failed to load output devices:", error);
|
||||||
set({ outputDevices: [DEFAULT_AUDIO_DEVICE] });
|
set({ outputDevices: [DEFAULT_AUDIO_DEVICE] });
|
||||||
|
|
@ -257,7 +221,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
// Play a test sound
|
// Play a test sound
|
||||||
playTestSound: async (soundType: "start" | "stop") => {
|
playTestSound: async (soundType: "start" | "stop") => {
|
||||||
try {
|
try {
|
||||||
await invoke("play_test_sound", { soundType });
|
await commands.playTestSound(soundType);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to play test sound (${soundType}):`, error);
|
console.error(`Failed to play test sound (${soundType}):`, error);
|
||||||
}
|
}
|
||||||
|
|
@ -265,8 +229,8 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
|
|
||||||
checkCustomSounds: async () => {
|
checkCustomSounds: async () => {
|
||||||
try {
|
try {
|
||||||
const sounds = await invoke("check_custom_sounds");
|
const sounds = await commands.checkCustomSounds();
|
||||||
get().setCustomSounds(sounds as { start: boolean; stop: boolean });
|
get().setCustomSounds(sounds);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check custom sounds:", error);
|
console.error("Failed to check custom sounds:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -306,9 +270,12 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
|
|
||||||
// Reset a setting to its default value
|
// Reset a setting to its default value
|
||||||
resetSetting: async (key) => {
|
resetSetting: async (key) => {
|
||||||
const defaultValue = DEFAULT_SETTINGS[key];
|
const { defaultSettings } = get();
|
||||||
if (defaultValue !== undefined) {
|
if (defaultSettings) {
|
||||||
await get().updateSetting(key, defaultValue as any);
|
const defaultValue = defaultSettings[key];
|
||||||
|
if (defaultValue !== undefined) {
|
||||||
|
await get().updateSetting(key, defaultValue as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -329,7 +296,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
bindings: {
|
bindings: {
|
||||||
...state.settings.bindings,
|
...state.settings.bindings,
|
||||||
[id]: {
|
[id]: {
|
||||||
...state.settings.bindings[id],
|
...state.settings.bindings[id]!,
|
||||||
current_binding: binding,
|
current_binding: binding,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -337,7 +304,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
: null,
|
: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await invoke("change_binding", { id, binding });
|
await commands.changeBinding(id, binding);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to update binding ${id}:`, error);
|
console.error(`Failed to update binding ${id}:`, error);
|
||||||
|
|
||||||
|
|
@ -350,7 +317,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
bindings: {
|
bindings: {
|
||||||
...state.settings.bindings,
|
...state.settings.bindings,
|
||||||
[id]: {
|
[id]: {
|
||||||
...state.settings.bindings[id],
|
...state.settings.bindings[id]!,
|
||||||
current_binding: originalBinding,
|
current_binding: originalBinding,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -371,7 +338,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
setUpdating(updateKey, true);
|
setUpdating(updateKey, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("reset_binding", { id });
|
await commands.resetBinding(id);
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to reset binding ${id}:`, error);
|
console.error(`Failed to reset binding ${id}:`, error);
|
||||||
|
|
@ -396,7 +363,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("set_post_process_provider", { providerId });
|
await commands.setPostProcessProvider(providerId);
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to set post-process provider:", error);
|
console.error("Failed to set post-process provider:", error);
|
||||||
|
|
@ -421,27 +388,16 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
const { setUpdating, refreshSettings } = get();
|
const { setUpdating, refreshSettings } = get();
|
||||||
const updateKey = `post_process_${settingType}:${providerId}`;
|
const updateKey = `post_process_${settingType}:${providerId}`;
|
||||||
|
|
||||||
// Map setting types to command names
|
|
||||||
const commandMap = {
|
|
||||||
base_url: "change_post_process_base_url_setting",
|
|
||||||
api_key: "change_post_process_api_key_setting",
|
|
||||||
model: "change_post_process_model_setting",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map setting types to param names
|
|
||||||
const paramMap = {
|
|
||||||
base_url: "baseUrl",
|
|
||||||
api_key: "apiKey",
|
|
||||||
model: "model",
|
|
||||||
};
|
|
||||||
|
|
||||||
setUpdating(updateKey, true);
|
setUpdating(updateKey, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke(commandMap[settingType], {
|
if (settingType === "base_url") {
|
||||||
providerId,
|
await commands.changePostProcessBaseUrlSetting(providerId, value);
|
||||||
[paramMap[settingType]]: value,
|
} else if (settingType === "api_key") {
|
||||||
});
|
await commands.changePostProcessApiKeySetting(providerId, value);
|
||||||
|
} else if (settingType === "model") {
|
||||||
|
await commands.changePostProcessModelSetting(providerId, value);
|
||||||
|
}
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
@ -480,12 +436,14 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call Tauri backend command instead of fetch
|
// Call Tauri backend command instead of fetch
|
||||||
const models: string[] = await invoke("fetch_post_process_models", {
|
const result = await commands.fetchPostProcessModels(providerId);
|
||||||
providerId,
|
if (result.status === "ok") {
|
||||||
});
|
setPostProcessModelOptions(providerId, result.data);
|
||||||
|
return result.data;
|
||||||
setPostProcessModelOptions(providerId, models);
|
} else {
|
||||||
return models;
|
console.error("Failed to fetch models:", result.error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch models:", error);
|
console.error("Failed to fetch models:", error);
|
||||||
// Don't cache empty array on error - let user retry
|
// Don't cache empty array on error - let user retry
|
||||||
|
|
@ -503,6 +461,20 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
// Load default settings from Rust
|
||||||
|
loadDefaultSettings: async () => {
|
||||||
|
try {
|
||||||
|
const result = await commands.getDefaultSettings();
|
||||||
|
if (result.status === "ok") {
|
||||||
|
set({ defaultSettings: result.data });
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load default settings:", result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load default settings:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Initialize everything
|
// Initialize everything
|
||||||
initialize: async () => {
|
initialize: async () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -510,8 +482,10 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
refreshAudioDevices,
|
refreshAudioDevices,
|
||||||
refreshOutputDevices,
|
refreshOutputDevices,
|
||||||
checkCustomSounds,
|
checkCustomSounds,
|
||||||
|
loadDefaultSettings,
|
||||||
} = get();
|
} = get();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
loadDefaultSettings(),
|
||||||
refreshSettings(),
|
refreshSettings(),
|
||||||
refreshAudioDevices(),
|
refreshAudioDevices(),
|
||||||
refreshOutputDevices(),
|
refreshOutputDevices(),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Path Aliases */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/bindings": ["./src/bindings.ts"]
|
||||||
|
},
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,13 @@ const host = process.env.TAURI_DEV_HOST;
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
|
||||||
|
// Path aliases
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@/bindings": resolve(__dirname, "./src/bindings.ts"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Multiple entry points for main app and overlay
|
// Multiple entry points for main app and overlay
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue