diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ffc514a..38988d1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -72,6 +83,23 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -81,56 +109,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" -dependencies = [ - "windows-sys 0.60.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.60.2", -] - [[package]] name = "anyhow" version = "1.0.100" @@ -492,6 +470,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -538,6 +528,29 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "brotli" version = "8.0.2" @@ -565,6 +578,39 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byte-unit" +version = "5.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" +dependencies = [ + "rust_decimal", + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.24.0" @@ -790,12 +836,6 @@ dependencies = [ "objc", ] -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - [[package]] name = "combine" version = "4.6.7" @@ -1506,19 +1546,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "epoll" version = "4.4.0" @@ -1668,6 +1695,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -1792,6 +1828,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -2292,7 +2334,7 @@ dependencies = [ "chrono", "cpal", "enigo", - "env_logger 0.11.8", + "env_filter", "flate2", "futures-util", "hound", @@ -2315,6 +2357,7 @@ dependencies = [ "tauri-plugin-clipboard-manager", "tauri-plugin-fs", "tauri-plugin-global-shortcut", + "tauri-plugin-log", "tauri-plugin-macos-permissions", "tauri-plugin-opener", "tauri-plugin-os", @@ -2334,6 +2377,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -2341,7 +2387,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.12", ] [[package]] @@ -2856,12 +2902,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.12.1" @@ -2900,30 +2940,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "jiff" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.108", -] - [[package]] name = "jni" version = "0.21.1" @@ -3126,6 +3142,9 @@ name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +dependencies = [ + "value-bag", +] [[package]] name = "lru-slab" @@ -3565,6 +3584,15 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -3893,12 +3921,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "open" version = "5.3.2" @@ -4486,6 +4508,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pxfm" version = "0.1.25" @@ -4589,6 +4631,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -4824,6 +4872,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.12.24" @@ -4909,6 +4966,35 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rodio" version = "0.20.1" @@ -4976,6 +5062,22 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rust_decimal" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -5180,6 +5282,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "secrecy" version = "0.10.3" @@ -5477,6 +5585,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "0.3.11" @@ -6169,6 +6283,12 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.44" @@ -6385,6 +6505,28 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "tauri-plugin-log" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5709c792b8630290b5d9811a1f8fe983dd925fc87c7fc7f4923616458cd00b6" +dependencies = [ + "android_logger", + "byte-unit", + "fern", + "log", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "time", +] + [[package]] name = "tauri-plugin-macos-permissions" version = "2.3.0" @@ -6727,7 +6869,9 @@ checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -7027,7 +7171,7 @@ dependencies = [ "async-openai 0.29.6", "async-trait", "derive_builder", - "env_logger 0.10.2", + "env_logger", "hound", "log", "ndarray", @@ -7259,18 +7403,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.18.1" @@ -7294,6 +7438,12 @@ dependencies = [ "ringbuffer", ] +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + [[package]] name = "vcpkg" version = "0.2.15" @@ -8359,6 +8509,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3df8f10..9634047 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -34,6 +34,7 @@ tauri = { version = "2.9.1", features = [ "tray-icon", 'image-png', ] } +tauri-plugin-log = "2.7.1" tauri-plugin-opener = "2.5.2" tauri-plugin-store = "2.4.1" tauri-plugin-os = "2.3.2" @@ -49,8 +50,8 @@ cpal = "0.16.0" anyhow = "1.0.95" rubato = "0.16.2" hound = "3.5.1" -env_logger = "0.11.6" log = "0.4.25" +env_filter = "0.1.0" tokio = "1.43.0" vad-rs = { git = "https://github.com/cjpais/vad-rs", default-features = false } enigo = "0.6.1" diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index ff5556d..ad9c340 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -318,14 +318,14 @@ impl ShortcutAction for TranscribeAction { "Text pasted successfully in {:?}", paste_time.elapsed() ), - Err(e) => eprintln!("Failed to paste transcription: {}", e), + Err(e) => error!("Failed to paste transcription: {}", e), } // Hide the overlay after transcription is complete utils::hide_recording_overlay(&ah_clone); change_tray_icon(&ah_clone, TrayIconState::Idle); }) .unwrap_or_else(|e| { - eprintln!("Failed to run paste on main thread: {:?}", e); + error!("Failed to run paste on main thread: {:?}", e); utils::hide_recording_overlay(&ah); change_tray_icon(&ah, TrayIconState::Idle); }); @@ -359,7 +359,7 @@ struct TestAction; impl ShortcutAction for TestAction { fn start(&self, app: &AppHandle, binding_id: &str, shortcut_str: &str) { - println!( + log::info!( "Shortcut ID '{}': Started - {} (App: {})", // Changed "Pressed" to "Started" for consistency binding_id, shortcut_str, @@ -368,7 +368,7 @@ impl ShortcutAction for TestAction { } fn stop(&self, app: &AppHandle, binding_id: &str, shortcut_str: &str) { - println!( + log::info!( "Shortcut ID '{}': Stopped - {} (App: {})", // Changed "Released" to "Stopped" for consistency binding_id, shortcut_str, diff --git a/src-tauri/src/audio_feedback.rs b/src-tauri/src/audio_feedback.rs index b8179b9..39764d8 100644 --- a/src-tauri/src/audio_feedback.rs +++ b/src-tauri/src/audio_feedback.rs @@ -1,5 +1,6 @@ use crate::settings; use cpal::traits::{DeviceTrait, HostTrait}; +use log::{debug, error, warn}; use rodio::OutputStreamBuilder; use std::fs::File; use std::io::BufReader; @@ -21,7 +22,7 @@ fn play_sound(app: &AppHandle, resource_path: &str, base_dir: tauri::path::BaseD let audio_path = match app_handle.path().resolve(&resource_path, base_dir) { Ok(path) => path.to_path_buf(), Err(e) => { - eprintln!( + error!( "Failed to resolve audio file path '{}': {}", resource_path, e ); @@ -33,7 +34,7 @@ fn play_sound(app: &AppHandle, resource_path: &str, base_dir: tauri::path::BaseD let selected_device = settings.selected_output_device.clone(); if let Err(e) = play_audio_file(&audio_path, selected_device, volume) { - eprintln!("Failed to play sound '{}': {}", resource_path, e); + error!("Failed to play sound '{}': {}", resource_path, e); } }); } @@ -87,7 +88,7 @@ fn play_audio_file( ) -> Result<(), Box> { let stream_builder = if let Some(device_name) = selected_device { if device_name == "Default" { - println!("Using default device"); + debug!("Using default device"); OutputStreamBuilder::from_default_device()? } else { let host = crate::audio_toolkit::get_cpal_host(); @@ -104,13 +105,13 @@ fn play_audio_file( match found_device { Some(device) => OutputStreamBuilder::from_device(device)?, None => { - eprintln!("Device '{}' not found, using default device", device_name); + warn!("Device '{}' not found, using default device", device_name); OutputStreamBuilder::from_default_device()? } } } } else { - println!("Using default device"); + debug!("Using default device"); OutputStreamBuilder::from_default_device()? }; diff --git a/src-tauri/src/audio_toolkit/audio/recorder.rs b/src-tauri/src/audio_toolkit/audio/recorder.rs index faf7dbe..cb203d2 100644 --- a/src-tauri/src/audio_toolkit/audio/recorder.rs +++ b/src-tauri/src/audio_toolkit/audio/recorder.rs @@ -82,7 +82,7 @@ impl AudioRecorder { let sample_rate = config.sample_rate().0; let channels = config.channels() as usize; - println!( + log::info!( "Using device: {:?}\nSample rate: {}\nChannels: {}\nFormat: {:?}", thread_device.name(), sample_rate, @@ -188,14 +188,14 @@ impl AudioRecorder { } if sample_tx.send(output_buffer.clone()).is_err() { - eprintln!("Failed to send samples"); + log::error!("Failed to send samples"); } }; device.build_input_stream( &config.clone().into(), stream_cb, - |err| eprintln!("Stream error: {}", err), + |err| log::error!("Stream error: {}", err), None, ) } diff --git a/src-tauri/src/audio_toolkit/audio/resampler.rs b/src-tauri/src/audio_toolkit/audio/resampler.rs index 2ba2215..149d99b 100644 --- a/src-tauri/src/audio_toolkit/audio/resampler.rs +++ b/src-tauri/src/audio_toolkit/audio/resampler.rs @@ -55,7 +55,7 @@ impl FrameResampler { .process(&[&self.in_buf[..]], None) { // let duration = start.elapsed(); - // println!("Resampler took: {:?}", duration); + // log::debug!("Resampler took: {:?}", duration); self.emit_frames(&out[0], &mut emit); } self.in_buf.clear(); diff --git a/src-tauri/src/audio_toolkit/vad/silero.rs b/src-tauri/src/audio_toolkit/vad/silero.rs index 4b30983..2deec7e 100644 --- a/src-tauri/src/audio_toolkit/vad/silero.rs +++ b/src-tauri/src/audio_toolkit/vad/silero.rs @@ -43,8 +43,6 @@ impl VoiceActivityDetector for SileroVad { .compute(frame) .map_err(|e| anyhow::anyhow!("Silero VAD error: {e}"))?; - // println!("Silero VAD result: prob = {}", result.prob); - if result.prob > self.threshold { Ok(VadFrame::Speech(frame)) } else { diff --git a/src-tauri/src/audio_toolkit/vad/smoothed.rs b/src-tauri/src/audio_toolkit/vad/smoothed.rs index 50ef19a..c0e4616 100644 --- a/src-tauri/src/audio_toolkit/vad/smoothed.rs +++ b/src-tauri/src/audio_toolkit/vad/smoothed.rs @@ -47,7 +47,6 @@ impl VoiceActivityDetector for SmoothedVad { // 2. Delegate to the wrapped boolean VAD let is_voice = self.inner_vad.is_voice(frame)?; - // println!("Is Voice: {}", is_voice); match (self.in_speech, is_voice) { // Potential start of speech - need to accumulate onset frames diff --git a/src-tauri/src/clipboard.rs b/src-tauri/src/clipboard.rs index 155b099..7618a9e 100644 --- a/src-tauri/src/clipboard.rs +++ b/src-tauri/src/clipboard.rs @@ -3,6 +3,7 @@ use enigo::Enigo; use enigo::Key; use enigo::Keyboard; use enigo::Settings; +use log::info; use tauri::AppHandle; use tauri_plugin_clipboard_manager::ClipboardExt; @@ -138,7 +139,7 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> { let settings = get_settings(&app_handle); let paste_method = settings.paste_method; - println!("Using paste method: {:?}", paste_method); + info!("Using paste method: {:?}", paste_method); // Perform the paste operation match paste_method { diff --git a/src-tauri/src/commands/audio.rs b/src-tauri/src/commands/audio.rs index fe3aa5d..da17d07 100644 --- a/src-tauri/src/commands/audio.rs +++ b/src-tauri/src/commands/audio.rs @@ -2,6 +2,7 @@ use crate::audio_feedback; use crate::audio_toolkit::audio::{list_input_devices, list_output_devices}; use crate::managers::audio::{AudioRecordingManager, MicrophoneMode}; use crate::settings::{get_settings, write_settings}; +use log::warn; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tauri::{AppHandle, Manager}; @@ -153,7 +154,7 @@ pub fn play_test_sound(app: AppHandle, sound_type: String) { "start" => audio_feedback::SoundType::Start, "stop" => audio_feedback::SoundType::Stop, _ => { - eprintln!("Unknown sound type: {}", sound_type); + warn!("Unknown sound type: {}", sound_type); return; } }; diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 9e0d248..aedb2b5 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -3,8 +3,9 @@ pub mod history; pub mod models; pub mod transcription; -use crate::utils::cancel_current_operation; +use crate::{settings, utils::cancel_current_operation}; use tauri::{AppHandle, Manager}; +use tauri_plugin_log::LogLevel; use tauri_plugin_opener::OpenerExt; #[tauri::command] @@ -22,6 +23,32 @@ pub fn get_app_dir_path(app: AppHandle) -> Result { Ok(app_data_dir.to_string_lossy().to_string()) } +#[tauri::command] +pub fn get_log_dir_path(app: AppHandle) -> Result { + let log_dir = app + .path() + .app_log_dir() + .map_err(|e| format!("Failed to get log directory: {}", e))?; + + Ok(log_dir.to_string_lossy().to_string()) +} + +#[tauri::command] +pub fn set_log_level(app: AppHandle, level: LogLevel) -> Result<(), String> { + let log_level: log::Level = level.clone().into(); + // Update the file log level atomic so the filter picks up the new level + crate::FILE_LOG_LEVEL.store( + log_level.to_level_filter() as u8, + std::sync::atomic::Ordering::Relaxed, + ); + + let mut settings = settings::get_settings(&app); + settings.log_level = level; + settings::write_settings(&app, settings); + + Ok(()) +} + #[tauri::command] pub fn open_recordings_folder(app: AppHandle) -> Result<(), String> { let app_data_dir = app @@ -38,3 +65,33 @@ pub fn open_recordings_folder(app: AppHandle) -> Result<(), String> { Ok(()) } + +#[tauri::command] +pub fn open_log_dir(app: AppHandle) -> Result<(), String> { + let log_dir = app + .path() + .app_log_dir() + .map_err(|e| format!("Failed to get log directory: {}", e))?; + + let path = log_dir.to_string_lossy().as_ref().to_string(); + app.opener() + .open_path(path, None::) + .map_err(|e| format!("Failed to open log directory: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub fn open_app_data_dir(app: AppHandle) -> Result<(), String> { + let app_data_dir = app + .path() + .app_data_dir() + .map_err(|e| format!("Failed to get app data directory: {}", e))?; + + let path = app_data_dir.to_string_lossy().as_ref().to_string(); + app.opener() + .open_path(path, None::) + .map_err(|e| format!("Failed to open app data directory: {}", e))?; + + Ok(()) +} diff --git a/src-tauri/src/helpers/clamshell.rs b/src-tauri/src/helpers/clamshell.rs index 70b224b..120750c 100644 --- a/src-tauri/src/helpers/clamshell.rs +++ b/src-tauri/src/helpers/clamshell.rs @@ -79,7 +79,7 @@ mod tests { // This will run on macOS and should not panic let result = is_clamshell(); assert!(result.is_ok()); - println!("Clamshell state: {:?}", result.unwrap()); + let _ = result.unwrap(); } #[test] @@ -87,8 +87,6 @@ mod tests { fn test_has_builtin_display() { let result = has_builtin_display(); assert!(result.is_ok()); - if let Ok(has_builtin) = result { - println!("Has built-in display (is laptop): {}", has_builtin); - } + let _ = result.unwrap(); } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index be5ad0d..efc66e3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,11 +12,13 @@ mod shortcut; mod tray; mod utils; +use env_filter::Builder as EnvFilterBuilder; use managers::audio::AudioRecordingManager; use managers::history::HistoryManager; use managers::model::ModelManager; use managers::transcription::TranscriptionManager; use std::collections::HashMap; +use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; use tauri::image::Image; @@ -24,6 +26,45 @@ use tauri::tray::TrayIconBuilder; use tauri::Emitter; use tauri::{AppHandle, Manager}; use tauri_plugin_autostart::{MacosLauncher, ManagerExt}; +use tauri_plugin_log::{Builder as LogBuilder, RotationStrategy, Target, TargetKind}; + +// Global atomic to store the file log level filter +// We use u8 to store the log::LevelFilter as a number +pub static FILE_LOG_LEVEL: AtomicU8 = AtomicU8::new(log::LevelFilter::Debug as u8); + +fn level_filter_from_u8(value: u8) -> log::LevelFilter { + match value { + 0 => log::LevelFilter::Off, + 1 => log::LevelFilter::Error, + 2 => log::LevelFilter::Warn, + 3 => log::LevelFilter::Info, + 4 => log::LevelFilter::Debug, + 5 => log::LevelFilter::Trace, + _ => log::LevelFilter::Trace, + } +} + +fn build_console_filter() -> env_filter::Filter { + let mut builder = EnvFilterBuilder::new(); + + match std::env::var("RUST_LOG") { + Ok(spec) if !spec.trim().is_empty() => { + if let Err(err) = builder.try_parse(&spec) { + log::warn!( + "Ignoring invalid RUST_LOG value '{}': {}. Falling back to info-level console logging", + spec, + err + ); + builder.filter_level(log::LevelFilter::Info); + } + } + _ => { + builder.filter_level(log::LevelFilter::Info); + } + } + + builder.build() +} #[derive(Default)] struct ShortcutToggleStates { @@ -37,21 +78,21 @@ fn show_main_window(app: &AppHandle) { if let Some(main_window) = app.get_webview_window("main") { // First, ensure the window is visible if let Err(e) = main_window.show() { - eprintln!("Failed to show window: {}", e); + log::error!("Failed to show window: {}", e); } // Then, bring it to the front and give it focus if let Err(e) = main_window.set_focus() { - eprintln!("Failed to focus window: {}", e); + log::error!("Failed to focus window: {}", e); } // Optional: On macOS, ensure the app becomes active if it was an accessory #[cfg(target_os = "macos")] { if let Err(e) = app.set_activation_policy(tauri::ActivationPolicy::Regular) { - eprintln!("Failed to set activation policy to Regular: {}", e); + log::error!("Failed to set activation policy to Regular: {}", e); } } } else { - eprintln!("Main window not found."); + log::error!("Main window not found."); } } @@ -155,9 +196,34 @@ fn trigger_update_check(app: AppHandle) -> Result<(), String> { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - env_logger::init(); + // Parse console logging directives from RUST_LOG, falling back to info-level logging + // when the variable is unset + let console_filter = build_console_filter(); tauri::Builder::default() + .plugin( + LogBuilder::new() + .level(log::LevelFilter::Trace) // Set to most verbose level globally + .max_file_size(500_000) + .rotation_strategy(RotationStrategy::KeepOne) + .clear_targets() + .targets([ + // Console output respects RUST_LOG environment variable + Target::new(TargetKind::Stdout).filter({ + let console_filter = console_filter.clone(); + move |metadata| console_filter.enabled(metadata) + }), + // File logs respect the user's settings (stored in FILE_LOG_LEVEL atomic) + Target::new(TargetKind::LogDir { + file_name: Some("handy".into()), + }) + .filter(|metadata| { + let file_level = FILE_LOG_LEVEL.load(Ordering::Relaxed); + metadata.level() <= level_filter_from_u8(file_level) + }), + ]) + .build(), + ) .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { show_main_window(app); })) @@ -185,6 +251,9 @@ pub fn run() { .manage(Mutex::new(ShortcutToggleStates::default())) .setup(move |app| { let settings = settings::get_settings(&app.handle()); + let file_log_level: log::Level = settings.log_level.clone().into(); + // 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); let app_handle = app.handle().clone(); initialize_core_logic(&app_handle); @@ -209,12 +278,12 @@ pub fn run() { .app_handle() .set_activation_policy(tauri::ActivationPolicy::Accessory); if let Err(e) = res { - println!("Failed to set activation policy: {}", e); + log::error!("Failed to set activation policy: {}", e); } } } tauri::WindowEvent::ThemeChanged(theme) => { - println!("Theme changed to: {:?}", theme); + log::info!("Theme changed to: {:?}", theme); // Update tray icon to match new theme, maintaining idle state utils::change_tray_icon(&window.app_handle(), utils::TrayIconState::Idle); } @@ -253,7 +322,11 @@ pub fn run() { 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, diff --git a/src-tauri/src/managers/audio.rs b/src-tauri/src/managers/audio.rs index 3249991..8d86e61 100644 --- a/src-tauri/src/managers/audio.rs +++ b/src-tauri/src/managers/audio.rs @@ -2,7 +2,7 @@ use crate::audio_toolkit::{list_input_devices, vad::SmoothedVad, AudioRecorder, use crate::helpers::clamshell; use crate::settings::{get_settings, AppSettings}; use crate::utils; -use log::{debug, info}; +use log::{debug, error, info}; use std::sync::{Arc, Mutex}; use std::time::Instant; use tauri::Manager; @@ -339,7 +339,7 @@ impl AudioRecordingManager { // Ensure microphone is open in on-demand mode if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) { if let Err(e) = self.start_microphone_stream() { - eprintln!("Failed to open microphone stream: {e}"); + error!("Failed to open microphone stream: {e}"); return false; } } @@ -354,7 +354,7 @@ impl AudioRecordingManager { return true; } } - eprintln!("Recorder not available"); + error!("Recorder not available"); false } else { false @@ -384,12 +384,12 @@ impl AudioRecordingManager { match rec.stop() { Ok(buf) => buf, Err(e) => { - eprintln!("stop() failed: {e}"); + error!("stop() failed: {e}"); Vec::new() } } } else { - eprintln!("Recorder not available"); + error!("Recorder not available"); Vec::new() }; @@ -402,7 +402,7 @@ impl AudioRecordingManager { // Pad if very short let s_len = samples.len(); - // println!("Got {} samples", { s_len }); + // debug!("Got {} samples", s_len); if s_len < WHISPER_SAMPLE_RATE && s_len > 0 { let mut padded = samples; padded.resize(WHISPER_SAMPLE_RATE * 5 / 4, 0.0); diff --git a/src-tauri/src/managers/model.rs b/src-tauri/src/managers/model.rs index c664779..4ee4465 100644 --- a/src-tauri/src/managers/model.rs +++ b/src-tauri/src/managers/model.rs @@ -2,6 +2,7 @@ use crate::settings::{get_settings, write_settings}; use anyhow::Result; use flate2::read::GzDecoder; use futures_util::StreamExt; +use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -225,9 +226,9 @@ impl ModelManager { // Only copy if user doesn't already have the model if !user_path.exists() { - println!("Migrating bundled model {} to user directory", filename); + info!("Migrating bundled model {} to user directory", filename); fs::copy(&bundled_path, &user_path)?; - println!("Successfully migrated {}", filename); + info!("Successfully migrated {}", filename); } } } @@ -250,7 +251,7 @@ impl ModelManager { // Clean up any leftover .extracting directories from interrupted extractions if extracting_path.exists() { - println!("Cleaning up interrupted extraction for model: {}", model.id); + warn!("Cleaning up interrupted extraction for model: {}", model.id); let _ = fs::remove_dir_all(&extracting_path); } @@ -292,7 +293,7 @@ impl ModelManager { // Find the first available (downloaded) model let models = self.available_models.lock().unwrap(); if let Some(available_model) = models.values().find(|model| model.is_downloaded) { - println!( + info!( "Auto-selecting model: {} ({})", available_model.id, available_model.name ); @@ -302,7 +303,7 @@ impl ModelManager { updated_settings.selected_model = available_model.id.clone(); write_settings(&self.app_handle, updated_settings); - println!("Successfully auto-selected model: {}", available_model.id); + info!("Successfully auto-selected model: {}", available_model.id); } } @@ -339,10 +340,10 @@ impl ModelManager { // Check if we have a partial download to resume let resume_from = if partial_path.exists() { let size = partial_path.metadata()?.len(); - println!("Resuming download of model {} from byte {}", model_id, size); + info!("Resuming download of model {} from byte {}", model_id, size); size } else { - println!("Starting fresh download of model {} from {}", model_id, url); + info!("Starting fresh download of model {} from {}", model_id, url); 0 }; @@ -456,7 +457,7 @@ impl ModelManager { if model_info.is_directory { // Emit extraction started event let _ = self.app_handle.emit("model-extraction-started", model_id); - println!("Extracting archive for directory-based model: {}", model_id); + info!("Extracting archive for directory-based model: {}", model_id); // Use a temporary extraction directory to ensure atomic operations let temp_extract_dir = self @@ -515,7 +516,7 @@ impl ModelManager { fs::rename(&temp_extract_dir, &final_model_dir)?; } - println!("Successfully extracted archive for model: {}", model_id); + info!("Successfully extracted archive for model: {}", model_id); // Emit extraction completed event let _ = self.app_handle.emit("model-extraction-completed", model_id); @@ -539,7 +540,7 @@ impl ModelManager { // Emit completion event let _ = self.app_handle.emit("model-download-complete", model_id); - println!( + info!( "Successfully downloaded model {} to {:?}", model_id, model_path ); @@ -548,7 +549,7 @@ impl ModelManager { } pub fn delete_model(&self, model_id: &str) -> Result<()> { - println!("ModelManager: delete_model called for: {}", model_id); + debug!("ModelManager: delete_model called for: {}", model_id); let model_info = { let models = self.available_models.lock().unwrap(); @@ -558,43 +559,40 @@ impl ModelManager { let model_info = model_info.ok_or_else(|| anyhow::anyhow!("Model not found: {}", model_id))?; - println!("ModelManager: Found model info: {:?}", model_info); + debug!("ModelManager: Found model info: {:?}", model_info); let model_path = self.models_dir.join(&model_info.filename); let partial_path = self .models_dir .join(format!("{}.partial", &model_info.filename)); - println!("ModelManager: Model path: {:?}", model_path); - println!("ModelManager: Partial path: {:?}", partial_path); + debug!("ModelManager: Model path: {:?}", model_path); + debug!("ModelManager: Partial path: {:?}", partial_path); let mut deleted_something = false; if model_info.is_directory { // Delete complete model directory if it exists if model_path.exists() && model_path.is_dir() { - println!( - "ModelManager: Deleting model directory at: {:?}", - model_path - ); + info!("Deleting model directory at: {:?}", model_path); fs::remove_dir_all(&model_path)?; - println!("ModelManager: Model directory deleted successfully"); + info!("Model directory deleted successfully"); deleted_something = true; } } else { // Delete complete model file if it exists if model_path.exists() { - println!("ModelManager: Deleting model file at: {:?}", model_path); + info!("Deleting model file at: {:?}", model_path); fs::remove_file(&model_path)?; - println!("ModelManager: Model file deleted successfully"); + info!("Model file deleted successfully"); deleted_something = true; } } // Delete partial file if it exists (same for both types) if partial_path.exists() { - println!("ModelManager: Deleting partial file at: {:?}", partial_path); + info!("Deleting partial file at: {:?}", partial_path); fs::remove_file(&partial_path)?; - println!("ModelManager: Partial file deleted successfully"); + info!("Partial file deleted successfully"); deleted_something = true; } @@ -604,7 +602,7 @@ impl ModelManager { // Update download status self.update_download_status()?; - println!("ModelManager: Download status updated"); + debug!("ModelManager: download status updated"); Ok(()) } @@ -655,7 +653,7 @@ impl ModelManager { } pub fn cancel_download(&self, model_id: &str) -> Result<()> { - println!("ModelManager: cancel_download called for: {}", model_id); + debug!("ModelManager: cancel_download called for: {}", model_id); let _model_info = { let models = self.available_models.lock().unwrap(); @@ -680,7 +678,7 @@ impl ModelManager { // Update download status to reflect current state self.update_download_status()?; - println!("ModelManager: Download cancelled for: {}", model_id); + info!("Download cancelled for: {}", model_id); Ok(()) } } diff --git a/src-tauri/src/managers/transcription.rs b/src-tauri/src/managers/transcription.rs index ca345f0..d87b88e 100644 --- a/src-tauri/src/managers/transcription.rs +++ b/src-tauri/src/managers/transcription.rs @@ -2,7 +2,7 @@ use crate::audio_toolkit::apply_custom_words; use crate::managers::model::{EngineType, ModelManager}; use crate::settings::{get_settings, ModelUnloadTimeout}; use anyhow::Result; -use log::debug; +use log::{debug, error, info, warn}; use serde::Serialize; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Condvar, Mutex}; @@ -289,7 +289,7 @@ impl TranscriptionManager { thread::spawn(move || { let settings = get_settings(&self_clone.app_handle); if let Err(e) = self_clone.load_model(&settings.selected_model) { - eprintln!("Failed to load model: {}", e); + error!("Failed to load model: {}", e); } let mut is_loading = self_clone.is_loading.lock().unwrap(); *is_loading = false; @@ -314,10 +314,10 @@ impl TranscriptionManager { let st = std::time::Instant::now(); - println!("Audio vector length: {}", audio.len()); + debug!("Audio vector length: {}", audio.len()); if audio.len() == 0 { - println!("Empty audio vector"); + debug!("Empty audio vector"); return Ok(String::new()); } @@ -393,17 +393,29 @@ impl TranscriptionManager { } else { "" }; - println!("\ntook {}ms{}", (et - st).as_millis(), translation_note); + info!( + "Transcription completed in {}ms{}", + (et - st).as_millis(), + translation_note + ); + + let final_result = corrected_result.trim().to_string(); + + if final_result.is_empty() { + info!("Transcription result is empty"); + } else { + info!("Transcription result: {}", final_result); + } // Check if we should immediately unload the model after transcription if settings.model_unload_timeout == ModelUnloadTimeout::Immediately { - println!("⚡ Immediately unloading model after transcription"); + info!("Immediately unloading model after transcription"); if let Err(e) = self.unload_model() { - eprintln!("Failed to immediately unload model: {}", e); + error!("Failed to immediately unload model: {}", e); } } - Ok(corrected_result.trim().to_string()) + Ok(final_result) } } @@ -417,7 +429,7 @@ impl Drop for TranscriptionManager { // Wait for the thread to finish gracefully if let Some(handle) = self.watcher_handle.lock().unwrap().take() { if let Err(e) = handle.join() { - eprintln!("Failed to join idle watcher thread: {:?}", e); + warn!("Failed to join idle watcher thread: {:?}", e); } else { debug!("Idle watcher thread joined successfully"); } diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index c5a2d52..d2ba1bd 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -1,6 +1,8 @@ +use log::{debug, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tauri::AppHandle; +use tauri_plugin_log::LogLevel; use tauri_plugin_store::StoreExt; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -181,6 +183,8 @@ pub struct AppSettings { pub overlay_position: OverlayPosition, #[serde(default = "default_debug_mode")] pub debug_mode: bool, + #[serde(default = "default_log_level")] + pub log_level: LogLevel, #[serde(default)] pub custom_words: Vec, #[serde(default)] @@ -248,6 +252,10 @@ fn default_debug_mode() -> bool { false } +fn default_log_level() -> LogLevel { + LogLevel::Debug +} + fn default_word_correction_threshold() -> f64 { 0.18 } @@ -374,6 +382,7 @@ pub fn get_default_settings() -> AppSettings { selected_language: "auto".to_string(), overlay_position: OverlayPosition::Bottom, debug_mode: false, + log_level: default_log_level(), custom_words: Vec::new(), model_unload_timeout: ModelUnloadTimeout::Never, word_correction_threshold: default_word_correction_threshold(), @@ -425,11 +434,11 @@ pub fn load_or_create_app_settings(app: &AppHandle) -> AppSettings { // Parse the entire settings object match serde_json::from_value::(settings_value) { Ok(settings) => { - println!("Found existing settings: {:?}", settings); + debug!("Found existing settings: {:?}", settings); settings } Err(e) => { - println!("Failed to parse settings: {}", e); + warn!("Failed to parse settings: {}", e); // Fall back to default settings if parsing fails let default_settings = get_default_settings(); store.set("settings", serde_json::to_value(&default_settings).unwrap()); diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index 0f72663..1296ec6 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -1,3 +1,4 @@ +use log::{error, warn}; use serde::Serialize; use tauri::{AppHandle, Emitter, Manager}; use tauri_plugin_autostart::ManagerExt; @@ -16,7 +17,7 @@ pub fn init_shortcuts(app: &AppHandle) { // Register shortcuts with the bindings from settings for (_id, binding) in settings.bindings { if let Err(e) = _register_shortcut(app, binding) { - eprintln!("Failed to register shortcut {} during init: {}", _id, e); + error!("Failed to register shortcut {} during init: {}", _id, e); } } } @@ -41,7 +42,7 @@ pub fn change_binding( Some(binding) => binding.clone(), None => { let error_msg = format!("Binding with id '{}' not found", id); - eprintln!("change_binding error: {}", error_msg); + warn!("change_binding error: {}", error_msg); return Ok(BindingResponse { success: false, binding: None, @@ -53,12 +54,12 @@ pub fn change_binding( // Unregister the existing binding if let Err(e) = _unregister_shortcut(&app, binding_to_modify.clone()) { let error_msg = format!("Failed to unregister shortcut: {}", e); - eprintln!("change_binding error: {}", error_msg); + error!("change_binding error: {}", error_msg); } // Validate the new shortcut before we touch the current registration if let Err(e) = validate_shortcut_string(&binding) { - eprintln!("change_binding validation error: {}", e); + warn!("change_binding validation error: {}", e); return Err(e); } @@ -69,7 +70,7 @@ pub fn change_binding( // Register the new binding if let Err(e) = _register_shortcut(&app, updated_binding.clone()) { let error_msg = format!("Failed to register shortcut: {}", e); - eprintln!("change_binding error: {}", error_msg); + error!("change_binding error: {}", error_msg); return Ok(BindingResponse { success: false, binding: None, @@ -135,7 +136,7 @@ pub fn change_sound_theme_setting(app: AppHandle, theme: String) -> Result<(), S "pop" => SoundTheme::Pop, "custom" => SoundTheme::Custom, other => { - eprintln!("Invalid sound theme '{}', defaulting to marimba", other); + warn!("Invalid sound theme '{}', defaulting to marimba", other); SoundTheme::Marimba } }; @@ -168,7 +169,7 @@ pub fn change_overlay_position_setting(app: AppHandle, position: String) -> Resu "top" => OverlayPosition::Top, "bottom" => OverlayPosition::Bottom, other => { - eprintln!("Invalid overlay position '{}', defaulting to bottom", other); + warn!("Invalid overlay position '{}', defaulting to bottom", other); OverlayPosition::Bottom } }; @@ -271,7 +272,7 @@ pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(), #[cfg(not(target_os = "macos"))] "shift_insert" => PasteMethod::ShiftInsert, other => { - eprintln!("Invalid paste method '{}', defaulting to ctrl_v", other); + warn!("Invalid paste method '{}', defaulting to ctrl_v", other); PasteMethod::CtrlV } }; @@ -287,7 +288,7 @@ pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Re "dont_modify" => ClipboardHandling::DontModify, "copy_to_clipboard" => ClipboardHandling::CopyToClipboard, other => { - eprintln!( + warn!( "Invalid clipboard handling '{}', defaulting to dont_modify", other ); @@ -644,7 +645,7 @@ fn validate_shortcut_string(raw: &str) -> 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 Err(e) = _unregister_shortcut(&app, b) { - eprintln!("suspend_binding error for id '{}': {}", id, e); + error!("suspend_binding error for id '{}': {}", id, e); return Err(e); } } @@ -656,7 +657,7 @@ pub fn suspend_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 Err(e) = _register_shortcut(&app, b) { - eprintln!("resume_binding error for id '{}': {}", id, e); + error!("resume_binding error for id '{}': {}", id, e); return Err(e); } } @@ -666,7 +667,7 @@ pub fn resume_binding(app: AppHandle, id: String) -> Result<(), String> { fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> { // Validate human-level rules first if let Err(e) = validate_shortcut_string(&binding.current_binding) { - eprintln!( + warn!( "_register_shortcut validation error for binding '{}': {}", binding.current_binding, e ); @@ -681,7 +682,7 @@ fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), S "Failed to parse shortcut '{}': {}", binding.current_binding, e ); - eprintln!("_register_shortcut parse error: {}", error_msg); + error!("_register_shortcut parse error: {}", error_msg); return Err(error_msg); } }; @@ -689,7 +690,7 @@ fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), S // Prevent duplicate registrations that would silently shadow one another if app.global_shortcut().is_registered(shortcut) { let error_msg = format!("Shortcut '{}' is already in use", binding.current_binding); - eprintln!("_register_shortcut duplicate error: {}", error_msg); + warn!("_register_shortcut duplicate error: {}", error_msg); return Err(error_msg); } @@ -733,8 +734,8 @@ fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), S } } } else { - println!( - "Warning: No action defined in ACTION_MAP for shortcut ID '{}'. Shortcut: '{}', State: {:?}", + warn!( + "No action defined in ACTION_MAP for shortcut ID '{}'. Shortcut: '{}', State: {:?}", binding_id_for_closure, shortcut_string, event.state ); } @@ -742,7 +743,7 @@ fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), S }) .map_err(|e| { let error_msg = format!("Couldn't register shortcut '{}': {}", binding.current_binding, e); - eprintln!("_register_shortcut registration error: {}", error_msg); + error!("_register_shortcut registration error: {}", error_msg); error_msg })?; @@ -757,7 +758,7 @@ fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), "Failed to parse shortcut '{}' for unregistration: {}", binding.current_binding, e ); - eprintln!("_unregister_shortcut parse error: {}", error_msg); + error!("_unregister_shortcut parse error: {}", error_msg); return Err(error_msg); } }; @@ -767,7 +768,7 @@ fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), "Failed to unregister shortcut '{}': {}", binding.current_binding, e ); - eprintln!("_unregister_shortcut error: {}", error_msg); + error!("_unregister_shortcut error: {}", error_msg); error_msg })?; diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 2c6700e..763a145 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,6 +1,7 @@ use crate::actions::ACTION_MAP; use crate::managers::audio::AudioRecordingManager; use crate::ManagedToggleState; +use log::{info, warn}; use std::sync::Arc; use tauri::{AppHandle, Manager}; @@ -13,7 +14,7 @@ pub use crate::tray::*; /// Centralized cancellation function that can be called from anywhere in the app. /// Handles cancelling both recording and transcription operations and updates UI state. pub fn cancel_current_operation(app: &AppHandle) { - println!("Initiating operation cancellation..."); + info!("Initiating operation cancellation..."); // First, reset all shortcut toggle states and call stop actions // This is critical for non-push-to-talk mode where shortcuts toggle on/off @@ -28,7 +29,7 @@ pub fn cancel_current_operation(app: &AppHandle) { .collect(); for binding_id in active_bindings { - println!("Stopping active action for binding: {}", binding_id); + info!("Stopping active action for binding: {}", binding_id); // Call the action's stop method to ensure proper cleanup if let Some(action) = ACTION_MAP.get(&binding_id) { @@ -41,7 +42,7 @@ pub fn cancel_current_operation(app: &AppHandle) { } } } else { - eprintln!("Warning: Failed to lock toggle state manager during cancellation"); + warn!("Failed to lock toggle state manager during cancellation"); } // Cancel any ongoing recording @@ -51,5 +52,5 @@ pub fn cancel_current_operation(app: &AppHandle) { // Update tray icon and menu to idle state change_tray_icon(app, crate::tray::TrayIconState::Idle); - println!("Operation cancellation completed - returned to idle state"); + info!("Operation cancellation completed - returned to idle state"); } diff --git a/src/components/settings/AppDataDirectory.tsx b/src/components/settings/AppDataDirectory.tsx index 4af03a0..8549970 100644 --- a/src/components/settings/AppDataDirectory.tsx +++ b/src/components/settings/AppDataDirectory.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { invoke } from "@tauri-apps/api/core"; -import { TextDisplay } from "../ui"; +import { SettingContainer } from "../ui/SettingContainer"; +import { Button } from "../ui/Button"; interface AppDataDirectoryProps { descriptionMode?: "tooltip" | "inline"; @@ -32,9 +33,13 @@ export const AppDataDirectory: React.FC = ({ loadAppDirectory(); }, []); - const handleCopy = (value: string) => { - // Could add a toast notification here if desired - console.log("Copied to clipboard:", value); + const handleOpen = async () => { + if (!appDirPath) return; + try { + await invoke("open_app_data_dir"); + } catch (openError) { + console.error("Failed to open app data directory:", openError); + } }; if (loading) { @@ -57,15 +62,27 @@ export const AppDataDirectory: React.FC = ({ } return ( - + layout="stacked" + > +
+
+ {appDirPath} +
+ +
+ ); }; diff --git a/src/components/settings/debug/DebugSettings.tsx b/src/components/settings/debug/DebugSettings.tsx index a4a624c..35f20b9 100644 --- a/src/components/settings/debug/DebugSettings.tsx +++ b/src/components/settings/debug/DebugSettings.tsx @@ -1,5 +1,7 @@ import React from "react"; import { WordCorrectionThreshold } from "./WordCorrectionThreshold"; +import { LogDirectory } from "./LogDirectory"; +import { LogLevelSelector } from "./LogLevelSelector"; import { SettingsGroup } from "../../ui/SettingsGroup"; import { HistoryLimit } from "../HistoryLimit"; import { AlwaysOnMicrophone } from "../AlwaysOnMicrophone"; @@ -13,6 +15,8 @@ export const DebugSettings: React.FC = () => { return (
+ + = ({ + descriptionMode = "tooltip", + grouped = false, +}) => { + const [logDir, setLogDir] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadLogDirectory = async () => { + try { + const result = await invoke("get_log_dir_path"); + setLogDir(result); + } catch (err) { + const errorMessage = + err && typeof err === "object" && "message" in err + ? String(err.message) + : "Failed to load log directory"; + setError(errorMessage); + } finally { + setLoading(false); + } + }; + + loadLogDirectory(); + }, []); + + const handleOpen = async () => { + if (!logDir) return; + try { + await invoke("open_log_dir"); + } catch (openError) { + console.error("Failed to open log directory:", openError); + } + }; + + return ( + + {loading ? ( +
+
+
+ ) : error ? ( +
+ Error loading log directory: {error} +
+ ) : ( +
+
+ {logDir} +
+ +
+ )} + + ); +}; diff --git a/src/components/settings/debug/LogLevelSelector.tsx b/src/components/settings/debug/LogLevelSelector.tsx new file mode 100644 index 0000000..c88fb97 --- /dev/null +++ b/src/components/settings/debug/LogLevelSelector.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { SettingContainer } from "../../ui/SettingContainer"; +import { Dropdown, type DropdownOption } from "../../ui/Dropdown"; +import { useSettings } from "../../../hooks/useSettings"; + +const LOG_LEVEL_OPTIONS: DropdownOption[] = [ + { value: "5", label: "Error" }, + { value: "4", label: "Warn" }, + { value: "3", label: "Info" }, + { value: "2", label: "Debug" }, + { value: "1", label: "Trace" }, +]; + +interface LogLevelSelectorProps { + descriptionMode?: "tooltip" | "inline"; + grouped?: boolean; +} + +export const LogLevelSelector: React.FC = ({ + descriptionMode = "tooltip", + grouped = false, +}) => { + const { settings, updateSetting, isUpdating } = useSettings(); + const currentLevel = settings?.log_level ?? 2; + const isLevelUpdating = isUpdating("log_level"); + + const selectedValue = currentLevel.toString(); + + const handleSelect = async (value: string) => { + const parsed = Number.parseInt(value, 10); + if (Number.isNaN(parsed) || parsed === currentLevel) { + return; + } + + try { + await updateSetting("log_level", parsed as typeof currentLevel); + } catch (error) { + console.error("Failed to update log level:", error); + } + }; + + return ( + +
+ +
+
+ ); +}; diff --git a/src/components/settings/debug/index.ts b/src/components/settings/debug/index.ts index 8c257be..325c09f 100644 --- a/src/components/settings/debug/index.ts +++ b/src/components/settings/debug/index.ts @@ -1 +1,3 @@ export { WordCorrectionThreshold } from "./WordCorrectionThreshold"; +export { LogDirectory } from "./LogDirectory"; +export { LogLevelSelector } from "./LogLevelSelector"; diff --git a/src/lib/types.ts b/src/lib/types.ts index aa649a5..b56433d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -43,6 +43,9 @@ export const ClipboardHandlingSchema = z.enum([ ]); export type ClipboardHandling = z.infer; +export const LogLevelSchema = z.number().int().min(1).max(5).default(2); +export type LogLevelValue = z.infer; + export const RecordingRetentionPeriodSchema = z.enum([ "never", "preserve_limit", @@ -96,6 +99,7 @@ export const SettingsSchema = z.object({ 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), diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index 461629f..adecaa7 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -72,6 +72,7 @@ const DEFAULT_SETTINGS: Partial = { selected_language: "auto", overlay_position: "bottom", debug_mode: false, + log_level: 2, custom_words: [], history_limit: 5, recording_retention_period: "preserve_limit", @@ -136,6 +137,7 @@ const settingUpdaters: { invoke("set_post_process_selected_prompt", { id: value }), mute_while_recording: (value) => invoke("change_mute_while_recording_setting", { enabled: value }), + log_level: (value) => invoke("set_log_level", { level: value }), }; export const useSettingsStore = create()(