From 0ad663c835276158966af0475523bd8814c505af Mon Sep 17 00:00:00 2001 From: "dingfeng.wong" Date: Tue, 22 Jul 2025 22:11:37 +0800 Subject: [PATCH] test --- STT_STATUSBAR_USAGE.md | 116 +++++++++ realtimesst.log | 17 ++ src/tooling/stt_cli.py | 570 ++++++++++++++++++++--------------------- 3 files changed, 418 insertions(+), 285 deletions(-) create mode 100644 STT_STATUSBAR_USAGE.md create mode 100644 realtimesst.log diff --git a/STT_STATUSBAR_USAGE.md b/STT_STATUSBAR_USAGE.md new file mode 100644 index 0000000..edc0780 --- /dev/null +++ b/STT_STATUSBAR_USAGE.md @@ -0,0 +1,116 @@ +# STT Status Bar App Usage + +## Overview + +The STT (Speech-to-Text) status bar app provides a convenient macOS menu bar interface for controlling speech-to-text functionality using wake word activation. + +## Features + +- **Start/Stop/Pause/Resume**: Full control over STT recording +- **Status Indicators**: Visual status in menu bar (πŸŽ™οΈπŸ”΄ recording, πŸŽ™οΈβΈοΈ paused, πŸŽ™οΈβš« stopped) +- **Configurable Settings**: Change wake words and models on the fly +- **File Output**: Save transcriptions to a file +- **Notifications**: Real-time notifications for transcriptions and status changes + +## Usage + +### Starting the Status Bar App + +```bash +# Launch the status bar app +tooling stt statusbar + +# Or using the full CLI path +python -m tooling.cli stt statusbar +``` + +### Menu Options + +#### Main Controls +- **Start STT**: Begin speech-to-text with current settings +- **Stop STT**: Stop speech-to-text completely +- **Pause**: Temporarily pause recognition (keeps recorder alive) +- **Resume**: Resume recognition from pause + +#### Settings +- **Wake Word**: Choose from predefined options: + - jarvis (default) + - alexa + - hey google + - hey siri + - computer + +- **Model**: Select Whisper model: + - tiny (fastest, least accurate) + - base (default, good balance) + - small (more accurate) + - medium (most accurate, slower) + +#### File Management +- **Show Recent Transcriptions**: Display last 10 transcription entries +- **Save to File...**: Set output file for saving transcriptions + +### Status Indicators + +| Icon | Status | Description | +|------|--------|-------------| +| πŸŽ™οΈβš« | Stopped | STT is not running | +| πŸŽ™οΈπŸ”΄ | Recording | STT is active and listening | +| πŸŽ™οΈβΈοΈ | Paused | STT is paused but can be resumed | + +### Workflow Example + +1. **Launch the app**: `tooling stt statusbar` +2. **Look for the πŸŽ™οΈ icon** in your macOS menu bar +3. **Click the icon** to open the menu +4. **Set your preferences**: + - Choose wake word from Settings > Wake Word + - Select model from Settings > Model + - Optionally set output file with "Save to File..." +5. **Click "Start STT"** to begin +6. **Say your wake word** (e.g., "jarvis") to trigger recording +7. **Speak clearly** after the wake word is detected +8. **Get notifications** with your transcribed text +9. **Use Pause/Resume** as needed +10. **Click "Stop STT"** when done + +### Notifications + +The app provides notifications for: +- STT started/stopped/paused/resumed +- Transcribed speech (shows first 100 characters) +- Settings changes (when STT is running) +- File operations + +### File Output + +When you set an output file: +- Transcriptions are saved with timestamps +- Sessions are marked with start/end timestamps +- File is automatically created in ~/Documents/ by default +- Each transcription includes: `[HH:MM:SS] transcribed text` + +### Requirements + +- macOS (rumps requires macOS and PyObjC) +- RealtimeSTT library +- Working microphone +- Python 3.11+ + +### Troubleshooting + +- **No menu bar icon**: Make sure you're running on macOS and rumps is installed +- **No transcriptions**: Check microphone permissions and try speaking louder +- **Wake word not detected**: Try adjusting sensitivity or use a different wake word +- **High CPU usage**: Consider using the "tiny" model for better performance + +### Advanced Configuration + +The status bar app uses sensible defaults, but you can modify the underlying configuration by editing the STTStatusBarApp class in `src/tooling/stt_cli.py`. + +Default settings: +- Wake word: "jarvis" +- Model: "base" +- Sensitivity: 0.6 +- Device: auto-detect (CUDA if available, otherwise CPU) +- Realtime display: enabled \ No newline at end of file diff --git a/realtimesst.log b/realtimesst.log new file mode 100644 index 0000000..8c54f41 --- /dev/null +++ b/realtimesst.log @@ -0,0 +1,17 @@ +2025-07-22 22:10:54.507 - RealTimeSTT: realtimestt - INFO - Starting RealTimeSTT +2025-07-22 22:10:54.520 - RealTimeSTT: realtimestt - INFO - Initializing audio recording (creating pyAudio input stream, sample rate: 16000 buffer size: 512 +2025-07-22 22:10:54.523 - RealTimeSTT: realtimestt - INFO - Initializing faster_whisper realtime transcription model tiny, default device: cpu, compute type: default, device index: 0, download root: None +2025-07-22 22:10:55.181 - RealTimeSTT: realtimestt - DEBUG - Faster_whisper realtime speech to text transcription model initialized successfully +2025-07-22 22:10:55.181 - RealTimeSTT: realtimestt - ERROR - Wakeword engine unknown/unsupported or wake_words not specified. Please specify one of: pvporcupine, openwakeword. +NoneType: None +2025-07-22 22:10:55.181 - RealTimeSTT: realtimestt - INFO - Initializing WebRTC voice with Sensitivity 3 +2025-07-22 22:10:55.181 - RealTimeSTT: realtimestt - DEBUG - WebRTC VAD voice activity detection engine initialized successfully +2025-07-22 22:10:55.838 - RealTimeSTT: realtimestt - DEBUG - Silero VAD voice activity detection engine initialized successfully +2025-07-22 22:10:55.838 - RealTimeSTT: realtimestt - DEBUG - Starting realtime worker +2025-07-22 22:10:55.838 - RealTimeSTT: realtimestt - DEBUG - Waiting for main transcription model to start +2025-07-22 22:11:01.946 - RealTimeSTT: realtimestt - DEBUG - Main transcription model ready +2025-07-22 22:11:01.946 - RealTimeSTT: realtimestt - DEBUG - RealtimeSTT initialization completed successfully +2025-07-22 22:11:01.946 - RealTimeSTT: realtimestt - INFO - Setting listen time +2025-07-22 22:11:01.946 - RealTimeSTT: realtimestt - INFO - State changed from 'inactive' to 'listening' +2025-07-22 22:11:01.947 - RealTimeSTT: realtimestt - DEBUG - Waiting for recording start +2025-07-22 22:11:01.981 - RealTimeSTT: realtimestt - INFO - State changed from 'listening' to 'wakeword' diff --git a/src/tooling/stt_cli.py b/src/tooling/stt_cli.py index bf7972c..c3c7166 100644 --- a/src/tooling/stt_cli.py +++ b/src/tooling/stt_cli.py @@ -96,300 +96,300 @@ if RUMPS_AVAILABLE: def __init__(self, name="STT", **kwargs): super().__init__(name, **kwargs) - - # Initialize state - self.recorder = None - self.is_running = False - self.is_paused = False - self.output_file = None - self.transcription_thread = None - self.stop_event = threading.Event() - - # Configuration - self.wake_word = "jarvis" - self.model = "base" - self.language = "" - self.realtime = True - self.sensitivity = 0.6 - self.device = "auto" - self.save_to_file = None - - # Menu setup - self.menu = [ - rumps.MenuItem("Start STT", callback=self.start_stt), - rumps.MenuItem("Stop STT", callback=self.stop_stt), - rumps.separator, - rumps.MenuItem("Pause", callback=self.pause_stt), - rumps.MenuItem("Resume", callback=self.resume_stt), - rumps.separator, - { - "Settings": { - "Wake Word": { - "jarvis": rumps.MenuItem("jarvis", callback=self.set_wake_word), - "alexa": rumps.MenuItem("alexa", callback=self.set_wake_word), - "hey google": rumps.MenuItem("hey google", callback=self.set_wake_word), - "hey siri": rumps.MenuItem("hey siri", callback=self.set_wake_word), - "computer": rumps.MenuItem("computer", callback=self.set_wake_word), - }, - "Model": { - "tiny": rumps.MenuItem("tiny", callback=self.set_model), - "base": rumps.MenuItem("base", callback=self.set_model), - "small": rumps.MenuItem("small", callback=self.set_model), - "medium": rumps.MenuItem("medium", callback=self.set_model), - } - } - }, - rumps.separator, - rumps.MenuItem("Show Recent Transcriptions", callback=self.show_transcriptions), - rumps.MenuItem("Save to File...", callback=self.select_output_file), - rumps.separator, - rumps.MenuItem("Quit", callback=rumps.quit_application), - ] - - # Set initial menu states - self.update_menu_states() - self.set_current_wake_word() - self.set_current_model() - - def update_menu_states(self): - """Update menu item states based on current status.""" - if self.is_running: - if self.is_paused: - self.title = "πŸŽ™οΈβΈοΈ" - self.menu["Start STT"].set_callback(None) - self.menu["Stop STT"].set_callback(self.stop_stt) - self.menu["Pause"].set_callback(None) - self.menu["Resume"].set_callback(self.resume_stt) - else: - self.title = "πŸŽ™οΈπŸ”΄" - self.menu["Start STT"].set_callback(None) - self.menu["Stop STT"].set_callback(self.stop_stt) - self.menu["Pause"].set_callback(self.pause_stt) - self.menu["Resume"].set_callback(None) - else: - self.title = "πŸŽ™οΈβš«" - self.menu["Start STT"].set_callback(self.start_stt) - self.menu["Stop STT"].set_callback(None) - self.menu["Pause"].set_callback(None) - self.menu["Resume"].set_callback(None) - - def set_current_wake_word(self): - """Set checkmark for current wake word.""" - for word in ["jarvis", "alexa", "hey google", "hey siri", "computer"]: - self.menu["Settings"]["Wake Word"][word].state = (word == self.wake_word) - - def set_current_model(self): - """Set checkmark for current model.""" - for model in ["tiny", "base", "small", "medium"]: - self.menu["Settings"]["Model"][model].state = (model == self.model) - - def set_wake_word(self, sender): - """Set the wake word from menu selection.""" - self.wake_word = sender.title - self.set_current_wake_word() - if self.is_running: - rumps.notification("STT Settings", "Wake Word Changed", f"Restart STT to use '{self.wake_word}'") - - def set_model(self, sender): - """Set the model from menu selection.""" - self.model = sender.title - self.set_current_model() - if self.is_running: - rumps.notification("STT Settings", "Model Changed", f"Restart STT to use '{self.model}' model") - - def start_stt(self, _): - """Start STT functionality.""" - if self.is_running: - return - - try: - from RealtimeSTT import AudioToTextRecorder - except ImportError: - rumps.alert("RealtimeSTT not installed", "Please install with: pip install RealtimeSTT") - return - - try: - # Determine device - if self.device == "auto": - try: - import torch - self.device = "cuda" if torch.cuda.is_available() else "cpu" - except ImportError: - self.device = "cpu" - # Setup output file if specified - if self.save_to_file: - self.save_to_file.parent.mkdir(parents=True, exist_ok=True) - self.output_file = open(self.save_to_file, 'a', encoding='utf-8') - timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.output_file.write(f"\n=== STT Session Started: {timestamp} ===\n") - self.output_file.flush() - - # Configure recorder - recorder_config = { - "model": self.model, - "wake_words": self.wake_word, - "wake_words_sensitivity": self.sensitivity, - "device": self.device, - "on_recording_start": self.on_recording_start, - "on_recording_stop": self.on_recording_stop, - "on_wakeword_detected": self.on_wakeword_detected, - "on_wakeword_timeout": self.on_wakeword_timeout, - "on_wakeword_detection_start": self.on_wakeword_detection_start, - } - - if self.language: - recorder_config["language"] = self.language - - if self.realtime: - recorder_config.update({ - "enable_realtime_transcription": True, - "on_realtime_transcription_update": self.on_realtime_transcription, - }) - - # Initialize recorder - self.recorder = AudioToTextRecorder(**recorder_config) - - # Start transcription thread - self.is_running = True + # Initialize state + self.recorder = None + self.is_running = False self.is_paused = False - self.stop_event.clear() - self.transcription_thread = threading.Thread(target=self.transcription_loop, daemon=True) - self.transcription_thread.start() + self.output_file = None + self.transcription_thread = None + self.stop_event = threading.Event() + + # Configuration + self.wake_word = "jarvis" + self.model = "base" + self.language = "" + self.realtime = True + self.sensitivity = 0.6 + self.device = "auto" + self.save_to_file = None + + # Menu setup + self.menu = [ + rumps.MenuItem("Start STT", callback=self.start_stt), + rumps.MenuItem("Stop STT", callback=self.stop_stt), + rumps.separator, + rumps.MenuItem("Pause", callback=self.pause_stt), + rumps.MenuItem("Resume", callback=self.resume_stt), + rumps.separator, + { + "Settings": { + "Wake Word": { + "jarvis": rumps.MenuItem("jarvis", callback=self.set_wake_word), + "alexa": rumps.MenuItem("alexa", callback=self.set_wake_word), + "hey google": rumps.MenuItem("hey google", callback=self.set_wake_word), + "hey siri": rumps.MenuItem("hey siri", callback=self.set_wake_word), + "computer": rumps.MenuItem("computer", callback=self.set_wake_word), + }, + "Model": { + "tiny": rumps.MenuItem("tiny", callback=self.set_model), + "base": rumps.MenuItem("base", callback=self.set_model), + "small": rumps.MenuItem("small", callback=self.set_model), + "medium": rumps.MenuItem("medium", callback=self.set_model), + } + } + }, + rumps.separator, + rumps.MenuItem("Show Recent Transcriptions", callback=self.show_transcriptions), + rumps.MenuItem("Save to File...", callback=self.select_output_file), + rumps.separator, + rumps.MenuItem("Quit", callback=rumps.quit_application), + ] + + # Set initial menu states + self.update_menu_states() + self.set_current_wake_word() + self.set_current_model() + + def update_menu_states(self): + """Update menu item states based on current status.""" + if self.is_running: + if self.is_paused: + self.title = "πŸŽ™οΈβΈοΈ" + self.menu["Start STT"].set_callback(None) + self.menu["Stop STT"].set_callback(self.stop_stt) + self.menu["Pause"].set_callback(None) + self.menu["Resume"].set_callback(self.resume_stt) + else: + self.title = "πŸŽ™οΈπŸ”΄" + self.menu["Start STT"].set_callback(None) + self.menu["Stop STT"].set_callback(self.stop_stt) + self.menu["Pause"].set_callback(self.pause_stt) + self.menu["Resume"].set_callback(None) + else: + self.title = "πŸŽ™οΈβš«" + self.menu["Start STT"].set_callback(self.start_stt) + self.menu["Stop STT"].set_callback(None) + self.menu["Pause"].set_callback(None) + self.menu["Resume"].set_callback(None) + + def set_current_wake_word(self): + """Set checkmark for current wake word.""" + for word in ["jarvis", "alexa", "hey google", "hey siri", "computer"]: + self.menu["Settings"]["Wake Word"][word].state = (word == self.wake_word) + + def set_current_model(self): + """Set checkmark for current model.""" + for model in ["tiny", "base", "small", "medium"]: + self.menu["Settings"]["Model"][model].state = (model == self.model) + + def set_wake_word(self, sender): + """Set the wake word from menu selection.""" + self.wake_word = sender.title + self.set_current_wake_word() + if self.is_running: + rumps.notification("STT Settings", "Wake Word Changed", f"Restart STT to use '{self.wake_word}'") + + def set_model(self, sender): + """Set the model from menu selection.""" + self.model = sender.title + self.set_current_model() + if self.is_running: + rumps.notification("STT Settings", "Model Changed", f"Restart STT to use '{self.model}' model") + + def start_stt(self, _): + """Start STT functionality.""" + if self.is_running: + return + + try: + from RealtimeSTT import AudioToTextRecorder + except ImportError: + rumps.alert("RealtimeSTT not installed", "Please install with: pip install RealtimeSTT") + return + + try: + # Determine device + if self.device == "auto": + try: + import torch + self.device = "cuda" if torch.cuda.is_available() else "cpu" + except ImportError: + self.device = "cpu" + + # Setup output file if specified + if self.save_to_file: + self.save_to_file.parent.mkdir(parents=True, exist_ok=True) + self.output_file = open(self.save_to_file, 'a', encoding='utf-8') + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.output_file.write(f"\n=== STT Session Started: {timestamp} ===\n") + self.output_file.flush() + + # Configure recorder + recorder_config = { + "model": self.model, + "wake_words": self.wake_word, + "wake_words_sensitivity": self.sensitivity, + "device": self.device, + "on_recording_start": self.on_recording_start, + "on_recording_stop": self.on_recording_stop, + "on_wakeword_detected": self.on_wakeword_detected, + "on_wakeword_timeout": self.on_wakeword_timeout, + "on_wakeword_detection_start": self.on_wakeword_detection_start, + } + + if self.language: + recorder_config["language"] = self.language + + if self.realtime: + recorder_config.update({ + "enable_realtime_transcription": True, + "on_realtime_transcription_update": self.on_realtime_transcription, + }) + + # Initialize recorder + self.recorder = AudioToTextRecorder(**recorder_config) + + # Start transcription thread + self.is_running = True + self.is_paused = False + self.stop_event.clear() + self.transcription_thread = threading.Thread(target=self.transcription_loop, daemon=True) + self.transcription_thread.start() + + self.update_menu_states() + rumps.notification("STT Started", f"Wake word: {self.wake_word}", f"Model: {self.model} | Device: {self.device}") + + except Exception as e: + rumps.alert("STT Error", f"Failed to start STT: {str(e)}") + + def stop_stt(self, _): + """Stop STT functionality.""" + if not self.is_running: + return + + self.is_running = False + self.is_paused = False + self.stop_event.set() + + if self.recorder: + try: + self.recorder.shutdown() + except: + pass + self.recorder = None + + if self.output_file: + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.output_file.write(f"=== STT Session Ended: {timestamp} ===\n\n") + self.output_file.close() + self.output_file = None self.update_menu_states() - rumps.notification("STT Started", f"Wake word: {self.wake_word}", f"Model: {self.model} | Device: {self.device}") + rumps.notification("STT Stopped", "Speech-to-text has been stopped", "") + + def pause_stt(self, _): + """Pause STT functionality.""" + if not self.is_running or self.is_paused: + return - except Exception as e: - rumps.alert("STT Error", f"Failed to start STT: {str(e)}") - - def stop_stt(self, _): - """Stop STT functionality.""" - if not self.is_running: - return + self.is_paused = True + self.update_menu_states() + rumps.notification("STT Paused", "Speech recognition paused", "Resume from menu") - self.is_running = False - self.is_paused = False - self.stop_event.set() - - if self.recorder: - try: - self.recorder.shutdown() - except: - pass - self.recorder = None - - if self.output_file: - timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.output_file.write(f"=== STT Session Ended: {timestamp} ===\n\n") - self.output_file.close() - self.output_file = None - - self.update_menu_states() - rumps.notification("STT Stopped", "Speech-to-text has been stopped", "") - - def pause_stt(self, _): - """Pause STT functionality.""" - if not self.is_running or self.is_paused: - return - - self.is_paused = True - self.update_menu_states() - rumps.notification("STT Paused", "Speech recognition paused", "Resume from menu") - - def resume_stt(self, _): - """Resume STT functionality.""" - if not self.is_running or not self.is_paused: - return - - self.is_paused = False - self.update_menu_states() - rumps.notification("STT Resumed", "Speech recognition resumed", f"Listening for '{self.wake_word}'") - - def transcription_loop(self): - """Main transcription loop running in background thread.""" - while self.is_running and not self.stop_event.is_set(): - try: - if not self.is_paused and self.recorder: - text = self.recorder.text() - if text and text.strip(): - self.on_transcription_complete(text) - else: - time.sleep(0.1) # Small delay when paused - except Exception as e: - print(f"Transcription error: {e}") - time.sleep(1) # Longer delay on error - - def on_realtime_transcription(self, text: str): - """Handle real-time transcription updates.""" - # Could update a notification or log - pass - - def on_transcription_complete(self, text: str): - """Handle completed transcriptions.""" - if text.strip(): - # Show notification with transcription - rumps.notification("Transcription", "Speech detected:", text[:100] + ("..." if len(text) > 100 else "")) + def resume_stt(self, _): + """Resume STT functionality.""" + if not self.is_running or not self.is_paused: + return - # Save to file if specified - if self.output_file: - timestamp = datetime.datetime.now().strftime("%H:%M:%S") - self.output_file.write(f"[{timestamp}] {text}\n") - self.output_file.flush() - - def on_recording_start(self): - """Called when recording starts.""" - pass - - def on_recording_stop(self): - """Called when recording stops.""" - pass - - def on_wakeword_detected(self): - """Called when wake word is detected.""" - pass - - def on_wakeword_timeout(self): - """Called when wake word times out.""" - pass - - def on_wakeword_detection_start(self): - """Called when starting to listen for wake words.""" - pass - - def show_transcriptions(self, _): - """Show recent transcriptions in an alert.""" - if self.output_file and self.save_to_file and self.save_to_file.exists(): - try: - with open(self.save_to_file, 'r', encoding='utf-8') as f: - lines = f.readlines() - recent = ''.join(lines[-10:]) # Last 10 lines - if recent.strip(): - rumps.alert("Recent Transcriptions", recent) + self.is_paused = False + self.update_menu_states() + rumps.notification("STT Resumed", "Speech recognition resumed", f"Listening for '{self.wake_word}'") + + def transcription_loop(self): + """Main transcription loop running in background thread.""" + while self.is_running and not self.stop_event.is_set(): + try: + if not self.is_paused and self.recorder: + text = self.recorder.text() + if text and text.strip(): + self.on_transcription_complete(text) else: - rumps.alert("No Transcriptions", "No transcriptions found yet.") + time.sleep(0.1) # Small delay when paused + except Exception as e: + print(f"Transcription error: {e}") + time.sleep(1) # Longer delay on error + + def on_realtime_transcription(self, text: str): + """Handle real-time transcription updates.""" + # Could update a notification or log + pass + + def on_transcription_complete(self, text: str): + """Handle completed transcriptions.""" + if text.strip(): + # Show notification with transcription + rumps.notification("Transcription", "Speech detected:", text[:100] + ("..." if len(text) > 100 else "")) + + # Save to file if specified + if self.output_file: + timestamp = datetime.datetime.now().strftime("%H:%M:%S") + self.output_file.write(f"[{timestamp}] {text}\n") + self.output_file.flush() + + def on_recording_start(self): + """Called when recording starts.""" + pass + + def on_recording_stop(self): + """Called when recording stops.""" + pass + + def on_wakeword_detected(self): + """Called when wake word is detected.""" + pass + + def on_wakeword_timeout(self): + """Called when wake word times out.""" + pass + + def on_wakeword_detection_start(self): + """Called when starting to listen for wake words.""" + pass + + def show_transcriptions(self, _): + """Show recent transcriptions in an alert.""" + if self.output_file and self.save_to_file and self.save_to_file.exists(): + try: + with open(self.save_to_file, 'r', encoding='utf-8') as f: + lines = f.readlines() + recent = ''.join(lines[-10:]) # Last 10 lines + if recent.strip(): + rumps.alert("Recent Transcriptions", recent) + else: + rumps.alert("No Transcriptions", "No transcriptions found yet.") + except Exception as e: + rumps.alert("Error", f"Could not read transcriptions: {e}") + else: + rumps.alert("No File", "No output file configured. Use 'Save to File...' to set one.") + + def select_output_file(self, _): + """Select output file for saving transcriptions.""" + try: + # Simple file selection using input dialog + response = rumps.Window( + message="Enter filename for transcriptions:", + title="Save Transcriptions", + default_text="stt_transcriptions.txt", + ok="Save", + cancel="Cancel" + ).run() + + if response.clicked and response.text: + self.save_to_file = Path.home() / "Documents" / response.text + rumps.notification("File Set", "Transcriptions will be saved to:", str(self.save_to_file)) except Exception as e: - rumps.alert("Error", f"Could not read transcriptions: {e}") - else: - rumps.alert("No File", "No output file configured. Use 'Save to File...' to set one.") - - def select_output_file(self, _): - """Select output file for saving transcriptions.""" - try: - # Simple file selection using input dialog - response = rumps.Window( - message="Enter filename for transcriptions:", - title="Save Transcriptions", - default_text="stt_transcriptions.txt", - ok="Save", - cancel="Cancel" - ).run() - - if response.clicked and response.text: - self.save_to_file = Path.home() / "Documents" / response.text - rumps.notification("File Set", "Transcriptions will be saved to:", str(self.save_to_file)) - except Exception as e: - rumps.alert("Error", f"Could not set output file: {e}") + rumps.alert("Error", f"Could not set output file: {e}") @stt_app.command("listen")