Very basic version of the clipboard implemented.
This commit is contained in:
parent
3d774b8367
commit
6b088deaf5
4 changed files with 188 additions and 101 deletions
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
|
|
@ -1982,6 +1982,7 @@ dependencies = [
|
||||||
"tauri-plugin-macos-permissions",
|
"tauri-plugin-macos-permissions",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
|
"tokio",
|
||||||
"whisper-rs",
|
"whisper-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ samplerate = "0.2.4"
|
||||||
rig-core = "0.8.0"
|
rig-core = "0.8.0"
|
||||||
env_logger = "0.11.6"
|
env_logger = "0.11.6"
|
||||||
log = "0.4.25"
|
log = "0.4.25"
|
||||||
|
tokio = "1.43.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
whisper-rs = { version = "0.13.2", features = ["metal"] }
|
whisper-rs = { version = "0.13.2", features = ["metal"] }
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,166 @@ use log::{info, Level};
|
||||||
use managers::keybinding::KeyBindingManager;
|
use managers::keybinding::KeyBindingManager;
|
||||||
use managers::transcription::TranscriptionManager;
|
use managers::transcription::TranscriptionManager;
|
||||||
use managers::{audio::AudioRecordingManager, transcription};
|
use managers::{audio::AudioRecordingManager, transcription};
|
||||||
use rdev::Key;
|
use rdev::{simulate, EventType, Key, SimulateError};
|
||||||
use rig::{completion::Prompt, providers::anthropic};
|
use rig::{completion::Prompt, providers::anthropic};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::{thread, time};
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
|
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||||
|
|
||||||
|
fn try_send_event(event: &EventType) {
|
||||||
|
if let Err(SimulateError) = simulate(event) {
|
||||||
|
println!("We could not send {:?}", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(event: EventType) {
|
||||||
|
try_send_event(&event);
|
||||||
|
thread::sleep(time::Duration::from_millis(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_multiple(events: &[EventType]) {
|
||||||
|
events.iter().for_each(try_send_event);
|
||||||
|
thread::sleep(time::Duration::from_millis(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_paste() {
|
||||||
|
// Determine the modifier key based on the OS
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let modifier_key = Key::MetaLeft; // Command key on macOS
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let modifier_key = Key::ControlLeft; // Control key on other systems
|
||||||
|
|
||||||
|
// Press both keys
|
||||||
|
send(EventType::KeyPress(modifier_key));
|
||||||
|
send(EventType::KeyPress(Key::KeyV));
|
||||||
|
|
||||||
|
// Release both keys simultaneously
|
||||||
|
send_multiple(&[
|
||||||
|
EventType::KeyRelease(Key::KeyV),
|
||||||
|
EventType::KeyRelease(modifier_key),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Additional delay after the complete combination
|
||||||
|
// thread::sleep(time::Duration::from_millis(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(text: String, app_handle: tauri::AppHandle) {
|
||||||
|
let clipboard = app_handle.clipboard();
|
||||||
|
|
||||||
|
// get the current clipboard content
|
||||||
|
let clipboard_content = clipboard.read_text().unwrap_or_default();
|
||||||
|
|
||||||
|
clipboard.write_text(&text).unwrap();
|
||||||
|
send_paste();
|
||||||
|
|
||||||
|
// restore the clipboard
|
||||||
|
clipboard.write_text(&clipboard_content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_highlighted_text(app_handle: tauri::AppHandle) -> String {
|
||||||
|
let clipboard = app_handle.clipboard();
|
||||||
|
|
||||||
|
// save the clipboard content
|
||||||
|
let clipboard_content = clipboard.read_text().unwrap_or_default();
|
||||||
|
|
||||||
|
// empty the clipboard
|
||||||
|
clipboard.write_text("").unwrap();
|
||||||
|
|
||||||
|
// get the highlighted text
|
||||||
|
let highlighted_text = clipboard.read_text().unwrap_or_default();
|
||||||
|
|
||||||
|
// restore the clipboard content
|
||||||
|
clipboard.write_text(&clipboard_content).unwrap();
|
||||||
|
|
||||||
|
highlighted_text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_bindings(manager: &mut KeyBindingManager) {
|
||||||
|
manager.register(
|
||||||
|
"ctrl-meta".to_string(),
|
||||||
|
vec![Key::ControlRight, Key::MetaRight],
|
||||||
|
|ctx| {
|
||||||
|
info!("Ctrl+Meta pressed!");
|
||||||
|
ctx.recording_manager.try_start_recording("ctrl-meta");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
|ctx| {
|
||||||
|
info!("release being called from ctrl-meta");
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
Some(tauri::async_runtime::spawn(async move {
|
||||||
|
if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-meta") {
|
||||||
|
match ctx.transcription_manager.transcribe(samples) {
|
||||||
|
Ok(transcription) => {
|
||||||
|
println!("Transcription: {}", transcription);
|
||||||
|
paste(transcription, ctx.app_handle.clone());
|
||||||
|
}
|
||||||
|
Err(err) => println!("Transcription error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register LLM Call after Transcription
|
||||||
|
manager.register(
|
||||||
|
"shift-alt".to_string(),
|
||||||
|
vec![Key::ShiftLeft, Key::Alt],
|
||||||
|
|ctx| {
|
||||||
|
info!("Shift+Alt pressed!");
|
||||||
|
ctx.recording_manager.try_start_recording("shift-alt");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
|ctx| {
|
||||||
|
info!("release being called from shift-alt");
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
Some(tauri::async_runtime::spawn(async move {
|
||||||
|
if let Some(samples) = ctx.recording_manager.stop_recording("shift-alt") {
|
||||||
|
if let Ok(transcription) = ctx.transcription_manager.transcribe(samples) {
|
||||||
|
println!("Transcription: {}", transcription);
|
||||||
|
match ctx.sonnet.prompt(transcription).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let highlighted_text = get_highlighted_text(ctx.app_handle.clone());
|
||||||
|
println!("Highlighted Text: {}", highlighted_text);
|
||||||
|
println!("Sonnet response: {}", response);
|
||||||
|
paste(response, ctx.app_handle.clone());
|
||||||
|
}
|
||||||
|
Err(err) => println!("Sonnet error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.register(
|
||||||
|
"ctrl-alt-meta".to_string(),
|
||||||
|
vec![Key::ControlLeft, Key::Alt, Key::MetaLeft],
|
||||||
|
|ctx| {
|
||||||
|
info!("Ctrl+Alt+Meta pressed!");
|
||||||
|
ctx.recording_manager.try_start_recording("ctrl-alt-meta");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
|ctx| {
|
||||||
|
info!("release being called from ctrl-alt-meta");
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
Some(tauri::async_runtime::spawn(async move {
|
||||||
|
if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-alt-meta") {
|
||||||
|
let samples: Vec<f32> = samples; // explicit type annotation
|
||||||
|
match ctx.transcription_manager.transcribe(samples) {
|
||||||
|
Ok(transcription) => {
|
||||||
|
println!("Transcription: {}", transcription);
|
||||||
|
// Call LLM for code
|
||||||
|
}
|
||||||
|
Err(err) => println!("Transcription error: {}", err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("No samples recorded");
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
|
@ -27,106 +183,6 @@ pub fn run() {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let manager = Arc::new(Mutex::new(KeyBindingManager::new(
|
|
||||||
recording_manager.clone(),
|
|
||||||
transcription_manager.clone(),
|
|
||||||
sonnet.clone(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
// Register your key bindings
|
|
||||||
{
|
|
||||||
let mut manager = manager.lock().unwrap();
|
|
||||||
|
|
||||||
// Register Basic Transcription
|
|
||||||
manager.register(
|
|
||||||
"ctrl-meta".to_string(),
|
|
||||||
vec![Key::ControlRight, Key::MetaRight],
|
|
||||||
|ctx| {
|
|
||||||
info!("Ctrl+Meta pressed!");
|
|
||||||
ctx.recording_manager.try_start_recording("ctrl-meta");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
|ctx| {
|
|
||||||
info!("release being called from ctrl-meta");
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
Some(tauri::async_runtime::spawn(async move {
|
|
||||||
if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-meta") {
|
|
||||||
match ctx.transcription_manager.transcribe(samples) {
|
|
||||||
Ok(transcription) => println!("Transcription: {}", transcription),
|
|
||||||
Err(err) => println!("Transcription error: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register LLM Call after Transcription
|
|
||||||
manager.register(
|
|
||||||
"shift-alt".to_string(),
|
|
||||||
vec![Key::ShiftLeft, Key::Alt],
|
|
||||||
|ctx| {
|
|
||||||
info!("Shift+Alt pressed!");
|
|
||||||
ctx.recording_manager.try_start_recording("shift-alt");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
|ctx| {
|
|
||||||
info!("release being called from shift-alt");
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
Some(tauri::async_runtime::spawn(async move {
|
|
||||||
if let Some(samples) = ctx.recording_manager.stop_recording("shift-alt") {
|
|
||||||
if let Ok(transcription) = ctx.transcription_manager.transcribe(samples) {
|
|
||||||
println!("Transcription: {}", transcription);
|
|
||||||
match ctx.sonnet.prompt(transcription).await {
|
|
||||||
Ok(response) => println!("Sonnet response: {}", response),
|
|
||||||
Err(err) => println!("Sonnet error: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
manager.register(
|
|
||||||
"ctrl-alt-meta".to_string(),
|
|
||||||
vec![Key::ControlLeft, Key::Alt, Key::MetaLeft],
|
|
||||||
|ctx| {
|
|
||||||
info!("Ctrl+Alt+Meta pressed!");
|
|
||||||
ctx.recording_manager.try_start_recording("ctrl-alt-meta");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
|ctx| {
|
|
||||||
info!("release being called from ctrl-alt-meta");
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
Some(tauri::async_runtime::spawn(async move {
|
|
||||||
if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-alt-meta") {
|
|
||||||
let samples: Vec<f32> = samples; // explicit type annotation
|
|
||||||
match ctx.transcription_manager.transcribe(samples) {
|
|
||||||
Ok(transcription) => {
|
|
||||||
println!("Transcription: {}", transcription);
|
|
||||||
// Call LLM for code
|
|
||||||
}
|
|
||||||
Err(err) => println!("Transcription error: {}", err),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("No samples recorded");
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tauri::async_runtime::spawn({
|
|
||||||
let manager = manager.clone();
|
|
||||||
async move {
|
|
||||||
rdev::listen(move |event| {
|
|
||||||
if let Ok(manager) = manager.lock() {
|
|
||||||
manager.handle_event(&event);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
|
|
@ -136,6 +192,32 @@ pub fn run() {
|
||||||
))
|
))
|
||||||
.plugin(tauri_plugin_macos_permissions::init())
|
.plugin(tauri_plugin_macos_permissions::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.setup(move |app| {
|
||||||
|
let app_handle = app.handle().clone();
|
||||||
|
|
||||||
|
let manager = Arc::new(Mutex::new(KeyBindingManager::new(
|
||||||
|
recording_manager.clone(),
|
||||||
|
transcription_manager.clone(),
|
||||||
|
sonnet.clone(),
|
||||||
|
app_handle.clone(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut manager = manager.lock().unwrap();
|
||||||
|
register_bindings(&mut manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
let manager_clone = manager.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
rdev::listen(move |event| {
|
||||||
|
if let Ok(manager) = manager_clone.lock() {
|
||||||
|
manager.handle_event(&event);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.invoke_handler(tauri::generate_handler![])
|
.invoke_handler(tauri::generate_handler![])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub struct BindingContext {
|
||||||
pub recording_manager: Arc<AudioRecordingManager>,
|
pub recording_manager: Arc<AudioRecordingManager>,
|
||||||
pub transcription_manager: Arc<TranscriptionManager>,
|
pub transcription_manager: Arc<TranscriptionManager>,
|
||||||
pub sonnet: Arc<Agent<anthropic::completion::CompletionModel>>,
|
pub sonnet: Arc<Agent<anthropic::completion::CompletionModel>>,
|
||||||
|
pub app_handle: tauri::AppHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeyBinding {
|
pub struct KeyBinding {
|
||||||
|
|
@ -92,6 +93,7 @@ impl KeyBindingManager {
|
||||||
recording_manager: Arc<AudioRecordingManager>,
|
recording_manager: Arc<AudioRecordingManager>,
|
||||||
transcription_manager: Arc<TranscriptionManager>,
|
transcription_manager: Arc<TranscriptionManager>,
|
||||||
sonnet: Arc<Agent<anthropic::completion::CompletionModel>>,
|
sonnet: Arc<Agent<anthropic::completion::CompletionModel>>,
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bindings: Vec::new(),
|
bindings: Vec::new(),
|
||||||
|
|
@ -99,6 +101,7 @@ impl KeyBindingManager {
|
||||||
recording_manager,
|
recording_manager,
|
||||||
transcription_manager,
|
transcription_manager,
|
||||||
sonnet,
|
sonnet,
|
||||||
|
app_handle,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue