working? ability to package/run on macos
This commit is contained in:
parent
8cd40c391d
commit
6b545d3acd
18 changed files with 1142 additions and 146 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -16,6 +16,8 @@
|
||||||
"@tauri-apps/plugin-clipboard-manager": "~2",
|
"@tauri-apps/plugin-clipboard-manager": "~2",
|
||||||
"@tauri-apps/plugin-global-shortcut": "~2",
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@tauri-apps/plugin-stronghold": "~2",
|
||||||
|
"@tauri-apps/plugin-upload": "~2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"tailwindcss": "^4.0.2",
|
"tailwindcss": "^4.0.2",
|
||||||
|
|
|
||||||
967
src-tauri/Cargo.lock
generated
967
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -35,6 +35,8 @@ env_logger = "0.11.6"
|
||||||
log = "0.4.25"
|
log = "0.4.25"
|
||||||
tokio = "1.43.0"
|
tokio = "1.43.0"
|
||||||
vad-rs = "0.1.5"
|
vad-rs = "0.1.5"
|
||||||
|
tauri-plugin-upload = "2"
|
||||||
|
tauri-plugin-stronghold = "2"
|
||||||
|
|
||||||
[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"] }
|
||||||
|
|
|
||||||
10
src-tauri/Entitlements.plist
Normal file
10
src-tauri/Entitlements.plist
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.device.microphone</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
8
src-tauri/Info.plist
Normal file
8
src-tauri/Info.plist
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Request microphone access to transcribe audio locally</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
{
|
{
|
||||||
"identifier": "desktop-capability",
|
"identifier": "desktop-capability",
|
||||||
"platforms": [
|
"platforms": ["macOS", "windows", "linux"],
|
||||||
"macOS",
|
"windows": ["main"],
|
||||||
"windows",
|
"permissions": ["autostart:default", "global-shortcut:default"]
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"windows": [
|
|
||||||
"main"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"autostart:default",
|
|
||||||
"global-shortcut:default"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 28 KiB |
|
|
@ -8,6 +8,7 @@ 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 std::{thread, time};
|
||||||
|
use tauri::Manager;
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ fn try_send_event(event: &EventType) {
|
||||||
|
|
||||||
fn send(event: EventType) {
|
fn send(event: EventType) {
|
||||||
try_send_event(&event);
|
try_send_event(&event);
|
||||||
thread::sleep(time::Duration::from_millis(40));
|
thread::sleep(time::Duration::from_millis(60));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_paste() {
|
fn send_paste() {
|
||||||
|
|
@ -137,105 +138,100 @@ fn register_bindings(manager: &mut KeyBindingManager) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register LLM Call after Transcription
|
// Register LLM Call after Transcription
|
||||||
manager.register(
|
// manager.register(
|
||||||
"shift-alt".to_string(),
|
// "shift-alt".to_string(),
|
||||||
vec![Key::ShiftLeft, Key::Alt],
|
// vec![Key::ShiftLeft, Key::Alt],
|
||||||
|ctx| {
|
// |ctx| {
|
||||||
info!("Shift+Alt pressed!");
|
// info!("Shift+Alt pressed!");
|
||||||
ctx.recording_manager.try_start_recording("shift-alt");
|
// ctx.recording_manager.try_start_recording("shift-alt");
|
||||||
None
|
// None
|
||||||
},
|
// },
|
||||||
|ctx| {
|
// |ctx| {
|
||||||
info!("release being called from shift-alt");
|
// info!("release being called from shift-alt");
|
||||||
let ctx = ctx.clone();
|
// let ctx = ctx.clone();
|
||||||
Some(tauri::async_runtime::spawn(async move {
|
// Some(tauri::async_runtime::spawn(async move {
|
||||||
if let Some(samples) = ctx.recording_manager.stop_recording("shift-alt") {
|
// if let Some(samples) = ctx.recording_manager.stop_recording("shift-alt") {
|
||||||
if let Ok(transcription) = ctx.transcription_manager.transcribe(samples) {
|
// if let Ok(transcription) = ctx.transcription_manager.transcribe(samples) {
|
||||||
println!("Transcription: {}", transcription);
|
// println!("Transcription: {}", transcription);
|
||||||
|
|
||||||
let instruct = ctx
|
// let instruct = ctx
|
||||||
.anthropic
|
// .anthropic
|
||||||
.agent(anthropic::CLAUDE_3_5_SONNET)
|
// .agent(anthropic::CLAUDE_3_5_SONNET)
|
||||||
.preamble(INSTRUCT_SYS)
|
// .preamble(INSTRUCT_SYS)
|
||||||
.temperature(0.5)
|
// .temperature(0.5)
|
||||||
.build();
|
// .build();
|
||||||
|
|
||||||
let highlighted_text = get_highlighted_text(ctx.app_handle.clone());
|
// let highlighted_text = get_highlighted_text(ctx.app_handle.clone());
|
||||||
println!("Highlighted Text: {}", highlighted_text);
|
// println!("Highlighted Text: {}", highlighted_text);
|
||||||
let prompt = format!("{}\n\ncontext:{}\n", transcription, highlighted_text);
|
// let prompt = format!("{}\n\ncontext:{}\n", transcription, highlighted_text);
|
||||||
|
|
||||||
match instruct.prompt(prompt).await {
|
// match instruct.prompt(prompt).await {
|
||||||
Ok(response) => {
|
// Ok(response) => {
|
||||||
println!("Sonnet response: {}", response);
|
// println!("Sonnet response: {}", response);
|
||||||
paste(response, ctx.app_handle.clone());
|
// paste(response, ctx.app_handle.clone());
|
||||||
}
|
// }
|
||||||
Err(err) => println!("Sonnet error: {}", err),
|
// Err(err) => println!("Sonnet error: {}", err),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}))
|
// }))
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
manager.register(
|
// manager.register(
|
||||||
"ctrl-alt-meta".to_string(),
|
// "ctrl-alt-meta".to_string(),
|
||||||
vec![Key::ControlLeft, Key::Alt, Key::MetaLeft],
|
// vec![Key::ControlLeft, Key::Alt, Key::MetaLeft],
|
||||||
|ctx| {
|
// |ctx| {
|
||||||
info!("Ctrl+Alt+Meta pressed!");
|
// info!("Ctrl+Alt+Meta pressed!");
|
||||||
ctx.recording_manager.try_start_recording("ctrl-alt-meta");
|
// ctx.recording_manager.try_start_recording("ctrl-alt-meta");
|
||||||
None
|
// None
|
||||||
},
|
// },
|
||||||
|ctx| {
|
// |ctx| {
|
||||||
info!("release being called from ctrl-alt-meta");
|
// info!("release being called from ctrl-alt-meta");
|
||||||
let ctx = ctx.clone();
|
// let ctx = ctx.clone();
|
||||||
Some(tauri::async_runtime::spawn(async move {
|
// Some(tauri::async_runtime::spawn(async move {
|
||||||
if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-alt-meta") {
|
// if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-alt-meta") {
|
||||||
let samples: Vec<f32> = samples; // explicit type annotation
|
// let samples: Vec<f32> = samples; // explicit type annotation
|
||||||
match ctx.transcription_manager.transcribe(samples) {
|
// match ctx.transcription_manager.transcribe(samples) {
|
||||||
Ok(transcription) => {
|
// Ok(transcription) => {
|
||||||
println!("Transcription: {}", transcription);
|
// println!("Transcription: {}", transcription);
|
||||||
|
|
||||||
let code = ctx
|
// let code = ctx
|
||||||
.anthropic
|
// .anthropic
|
||||||
.agent(anthropic::CLAUDE_3_5_SONNET)
|
// .agent(anthropic::CLAUDE_3_5_SONNET)
|
||||||
.preamble(CODE_SYS)
|
// .preamble(CODE_SYS)
|
||||||
.temperature(0.5)
|
// .temperature(0.5)
|
||||||
.build();
|
// .build();
|
||||||
|
|
||||||
let highlighted_text = get_highlighted_text(ctx.app_handle.clone());
|
// let highlighted_text = get_highlighted_text(ctx.app_handle.clone());
|
||||||
let prompt =
|
// let prompt =
|
||||||
format!("{}\n\ncontext:{}\n", transcription, highlighted_text);
|
// format!("{}\n\ncontext:{}\n", transcription, highlighted_text);
|
||||||
|
|
||||||
match code.prompt(prompt).await {
|
// match code.prompt(prompt).await {
|
||||||
Ok(response) => {
|
// Ok(response) => {
|
||||||
println!("Sonnet response: {}", response);
|
// println!("Sonnet response: {}", response);
|
||||||
paste(response, ctx.app_handle.clone());
|
// paste(response, ctx.app_handle.clone());
|
||||||
}
|
// }
|
||||||
Err(err) => println!("Sonnet error: {}", err),
|
// Err(err) => println!("Sonnet error: {}", err),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Err(err) => println!("Transcription error: {}", err),
|
// Err(err) => println!("Transcription error: {}", err),
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
println!("No samples recorded");
|
// println!("No samples recorded");
|
||||||
}
|
// }
|
||||||
}))
|
// }))
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let recording_manager =
|
|
||||||
Arc::new(AudioRecordingManager::new().expect("Failed to initialize recording manager"));
|
|
||||||
let transcription_manager =
|
|
||||||
Arc::new(TranscriptionManager::new().expect("Failed to initialize transcription manager"));
|
|
||||||
// let transcription_manager = Arc::new(TranscriptionManager::new());
|
|
||||||
let claude_client = Arc::new(anthropic::Client::from_env());
|
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_stronghold::Builder::new(|pass| todo!()).build())
|
||||||
|
.plugin(tauri_plugin_upload::init())
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_autostart::init(
|
.plugin(tauri_plugin_autostart::init(
|
||||||
|
|
@ -247,10 +243,33 @@ pub fn run() {
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
|
|
||||||
|
let vad_path = app.path().resolve(
|
||||||
|
"resources/silero_vad_v4.onnx",
|
||||||
|
tauri::path::BaseDirectory::Resource,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let whisper_path = app.path().resolve(
|
||||||
|
"resources/ggml-small.bin",
|
||||||
|
tauri::path::BaseDirectory::Resource,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let recording_manager = Arc::new(
|
||||||
|
AudioRecordingManager::new(&vad_path)
|
||||||
|
.expect("Failed to initialize recording manager"),
|
||||||
|
);
|
||||||
|
let transcription_manager = Arc::new(
|
||||||
|
TranscriptionManager::new(
|
||||||
|
whisper_path
|
||||||
|
.to_str()
|
||||||
|
.expect("Path contains invalid UTF-8 Chars"),
|
||||||
|
)
|
||||||
|
.expect("Failed to initialize transcription manager"),
|
||||||
|
);
|
||||||
|
|
||||||
let manager = Arc::new(Mutex::new(KeyBindingManager::new(
|
let manager = Arc::new(Mutex::new(KeyBindingManager::new(
|
||||||
recording_manager.clone(),
|
recording_manager.clone(),
|
||||||
transcription_manager.clone(),
|
transcription_manager.clone(),
|
||||||
claude_client.clone(),
|
// claude_client.clone(),
|
||||||
app_handle.clone(),
|
app_handle.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use rubato::{FftFixedIn, Resampler};
|
use rubato::{FftFixedIn, Resampler};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use vad_rs::{Vad, VadStatus};
|
use vad_rs::{Vad, VadStatus};
|
||||||
|
|
@ -16,7 +17,7 @@ pub struct AudioRecordingManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioRecordingManager {
|
impl AudioRecordingManager {
|
||||||
pub fn new() -> Result<Self, anyhow::Error> {
|
pub fn new(vad_path: &PathBuf) -> Result<Self, anyhow::Error> {
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let device = host
|
let device = host
|
||||||
.default_input_device()
|
.default_input_device()
|
||||||
|
|
@ -28,9 +29,7 @@ impl AudioRecordingManager {
|
||||||
// Configure the resampler - keeping 1024 as input size for FFT efficiency
|
// Configure the resampler - keeping 1024 as input size for FFT efficiency
|
||||||
let resampler = FftFixedIn::new(sample_rate as usize, 16000, 1024, 2, 1)?;
|
let resampler = FftFixedIn::new(sample_rate as usize, 16000, 1024, 2, 1)?;
|
||||||
|
|
||||||
let vad = Arc::new(Mutex::new(
|
let vad = Arc::new(Mutex::new(Vad::new(vad_path, 16000).unwrap()));
|
||||||
Vad::new("resources/silero_vad_v4.onnx", 16000).unwrap(),
|
|
||||||
));
|
|
||||||
let vad_clone = Arc::clone(&vad);
|
let vad_clone = Arc::clone(&vad);
|
||||||
|
|
||||||
let state = Arc::new(Mutex::new(RecordingState::Idle));
|
let state = Arc::new(Mutex::new(RecordingState::Idle));
|
||||||
|
|
@ -82,15 +81,13 @@ impl AudioRecordingManager {
|
||||||
if let Ok(mut vad) = vad_clone.lock() {
|
if let Ok(mut vad) = vad_clone.lock() {
|
||||||
// println!("VAD lock acquired");
|
// println!("VAD lock acquired");
|
||||||
match vad.compute(&chunk) {
|
match vad.compute(&chunk) {
|
||||||
Ok(mut result) => match result.status() {
|
Ok(mut result) => {
|
||||||
VadStatus::Speech => {
|
if result.prob > 0.15 {
|
||||||
let mut buffer =
|
let mut buffer =
|
||||||
buffer_clone.lock().unwrap();
|
buffer_clone.lock().unwrap();
|
||||||
buffer.extend_from_slice(&chunk);
|
buffer.extend_from_slice(&chunk);
|
||||||
}
|
}
|
||||||
VadStatus::Silence => {}
|
}
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("Error computing VAD: {:?}", error)
|
eprintln!("Error computing VAD: {:?}", error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ type KeySet = HashSet<Key>;
|
||||||
pub struct BindingContext {
|
pub struct BindingContext {
|
||||||
pub recording_manager: Arc<AudioRecordingManager>,
|
pub recording_manager: Arc<AudioRecordingManager>,
|
||||||
pub transcription_manager: Arc<TranscriptionManager>,
|
pub transcription_manager: Arc<TranscriptionManager>,
|
||||||
pub anthropic: Arc<anthropic::Client>,
|
// pub anthropic: Arc<anthropic::Client>,
|
||||||
pub app_handle: tauri::AppHandle,
|
pub app_handle: tauri::AppHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ impl KeyBindingManager {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
recording_manager: Arc<AudioRecordingManager>,
|
recording_manager: Arc<AudioRecordingManager>,
|
||||||
transcription_manager: Arc<TranscriptionManager>,
|
transcription_manager: Arc<TranscriptionManager>,
|
||||||
anthropic: Arc<anthropic::Client>,
|
// anthropic: Arc<anthropic::Client>,
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -62,7 +62,7 @@ impl KeyBindingManager {
|
||||||
context: BindingContext {
|
context: BindingContext {
|
||||||
recording_manager,
|
recording_manager,
|
||||||
transcription_manager,
|
transcription_manager,
|
||||||
anthropic,
|
// anthropic,
|
||||||
app_handle,
|
app_handle,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,12 @@ pub struct TranscriptionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TranscriptionManager {
|
impl TranscriptionManager {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new(whisper_path: &str) -> Result<Self> {
|
||||||
install_whisper_log_trampoline();
|
install_whisper_log_trampoline();
|
||||||
// Load the model
|
// Load the model
|
||||||
let context = WhisperContext::new_with_params(
|
let context =
|
||||||
"resources/ggml-small.bin",
|
WhisperContext::new_with_params(whisper_path, WhisperContextParameters::default())
|
||||||
WhisperContextParameters::default(),
|
.map_err(|e| anyhow::anyhow!("Failed to load whisper model: {}", e))?;
|
||||||
)
|
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to load whisper model: {}", e))?;
|
|
||||||
|
|
||||||
// Create state
|
// Create state
|
||||||
let state = context.create_state().expect("failed to create state");
|
let state = context.create_state().expect("failed to create state");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "handy-app",
|
"productName": "Handy",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"identifier": "com.handy-app.app",
|
"identifier": "com.handy-app.app",
|
||||||
"build": {
|
"build": {
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "handy-app",
|
"title": "Handy",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600
|
"height": 600
|
||||||
}
|
}
|
||||||
|
|
@ -24,12 +24,20 @@
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
|
"resources": ["resources/*"],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
]
|
],
|
||||||
|
"macOS": {
|
||||||
|
"files": {},
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"minimumSystemVersion": "10.13",
|
||||||
|
"signingIdentity": "-",
|
||||||
|
"entitlements": "Entitlements.plist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
interface Shortcut {
|
interface Shortcut {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -11,23 +11,23 @@ export const KeyboardShortcuts: React.FC = () => {
|
||||||
// These would normally come from the backend configuration
|
// These would normally come from the backend configuration
|
||||||
const shortcuts: Shortcut[] = [
|
const shortcuts: Shortcut[] = [
|
||||||
{
|
{
|
||||||
id: 'transcribe',
|
id: "transcribe",
|
||||||
name: 'Transcribe',
|
name: "Transcribe",
|
||||||
description: 'Convert speech to text',
|
description: "Convert speech to text",
|
||||||
keys: ['⌃', '⌘'],
|
keys: ["⌃", "⌘"],
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'instruct',
|
|
||||||
name: 'AI Chat',
|
|
||||||
description: 'Start a conversation with AI',
|
|
||||||
keys: ['⇧', '⌥'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'code',
|
|
||||||
name: 'Generate Code',
|
|
||||||
description: 'Generate code from voice input',
|
|
||||||
keys: ['⌃', '⌥', '⌘'],
|
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// id: 'instruct',
|
||||||
|
// name: 'AI Chat',
|
||||||
|
// description: 'Start a conversation with AI',
|
||||||
|
// keys: ['⇧', '⌥'],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 'code',
|
||||||
|
// name: 'Generate Code',
|
||||||
|
// description: 'Generate code from voice input',
|
||||||
|
// keys: ['⌃', '⌥', '⌘'],
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -38,7 +38,9 @@ export const KeyboardShortcuts: React.FC = () => {
|
||||||
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:bg-gray-50"
|
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:bg-gray-50"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900">{shortcut.name}</h3>
|
<h3 className="text-sm font-medium text-gray-900">
|
||||||
|
{shortcut.name}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-gray-500">{shortcut.description}</p>
|
<p className="text-sm text-gray-500">{shortcut.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue