From 90a4e38308f20e6af05c22a6a2dbe6c12b5f736f Mon Sep 17 00:00:00 2001 From: jpierzchala Date: Fri, 21 Mar 2025 10:26:09 +0100 Subject: [PATCH 1/6] Improve clipboard handling with retry logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added a new static method safe_open_clipboard that attempts to open the clipboard repeatedly (with configurable retries and delay) to handle transient access issues. • Replaced direct win32clipboard.OpenClipboard calls in _paste_with_clipboard_preservation with safe_open_clipboard to safely preserve and later restore clipboard data. • Updated win32clipboard.SetClipboardText to use the CF_UNICODETEXT flag to ensure proper Unicode formatting. • Wrapped the clipboard closing calls in a try/except block to guarantee cleanup even if errors occur. • Added error messages if the clipboard cannot be opened for preservation or restoration. These changes improve the robustness and reliability of clipboard operations during simulated input actions. --- src/input_simulation.py | 70 +++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/src/input_simulation.py b/src/input_simulation.py index cda59d2e..29e39633 100644 --- a/src/input_simulation.py +++ b/src/input_simulation.py @@ -76,6 +76,20 @@ def typewrite(self, text): self._typewrite_ydotool(text, interval) elif self.input_method == 'dotool': self._typewrite_dotool(text, interval) + + @staticmethod + def safe_open_clipboard(max_retries=5, delay=0.1): + """ + Try to open the clipboard repeatedly. + Returns True if successful, else False. + """ + for _ in range(max_retries): + try: + win32clipboard.OpenClipboard() + return True + except Exception: + time.sleep(delay) + return False def _paste_with_clipboard_preservation(self, text): """ @@ -87,7 +101,9 @@ def _paste_with_clipboard_preservation(self, text): """ # Store all clipboard formats saved_formats = {} - win32clipboard.OpenClipboard() + if not InputSimulator.safe_open_clipboard(): + print("Unable to open clipboard for preserving original content.") + return try: # Get the list of available formats @@ -99,38 +115,44 @@ def _paste_with_clipboard_preservation(self, text): except: pass # Skip formats we can't handle format_id = win32clipboard.EnumClipboardFormats(format_id) - + # Clear clipboard and set our text win32clipboard.EmptyClipboard() - win32clipboard.SetClipboardText(text) - win32clipboard.CloseClipboard() - - # Simulate Ctrl+V - if self.input_method == 'pynput': - with self.keyboard.pressed(Key.ctrl): - self.keyboard.press('v') - self.keyboard.release('v') - elif self.input_method == 'ydotool': - run_command_or_exit_on_failure([ - "ydotool", "key", "ctrl+v" - ]) - elif self.input_method == 'dotool': - assert self.dotool_process and self.dotool_process.stdin - self.dotool_process.stdin.write("key ctrl+v\n") - self.dotool_process.stdin.flush() - - # Wait for paste to complete - time.sleep(0.1) + win32clipboard.SetClipboardText(text, win32con.CF_UNICODETEXT) + finally: + try: + win32clipboard.CloseClipboard() + except: + pass # Ensure clipboard is closed even if an error occurred + + # Simulate Ctrl+V + if self.input_method == 'pynput': + with self.keyboard.pressed(Key.ctrl): + self.keyboard.press('v') + self.keyboard.release('v') + elif self.input_method == 'ydotool': + run_command_or_exit_on_failure([ + "ydotool", "key", "ctrl+v" + ]) + elif self.input_method == 'dotool': + assert self.dotool_process and self.dotool_process.stdin + self.dotool_process.stdin.write("key ctrl+v\n") + self.dotool_process.stdin.flush() - # Restore all original clipboard formats - win32clipboard.OpenClipboard() + # Wait for paste to complete + time.sleep(0.1) + + # Restore all original clipboard formats + if not InputSimulator.safe_open_clipboard(): + print("Unable to reopen clipboard for restoring original content.") + return + try: win32clipboard.EmptyClipboard() for format_id, data in saved_formats.items(): try: win32clipboard.SetClipboardData(format_id, data) except: pass # Skip if we can't restore a particular format - finally: try: win32clipboard.CloseClipboard() From 61ca88e5e50ce18dc6ec7900f31547f9061ed19f Mon Sep 17 00:00:00 2001 From: jpierzchala Date: Fri, 21 Mar 2025 11:35:40 +0100 Subject: [PATCH 2/6] Title: fix(settings_window): remove unintended file content concatenation Body: Previously, when reading a file in the settings window the file's content was appended to the existing text in the text editor. This led to unexpected modifications of the displayed text. With this change, the editor now retains its current text without appending additional content from the file. File contents are only appended for the api call. --- src/ui/settings_window.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/settings_window.py b/src/ui/settings_window.py index 0bdd7c75..6890f882 100644 --- a/src/ui/settings_window.py +++ b/src/ui/settings_window.py @@ -771,10 +771,7 @@ def browse_system_message_file(self, file_edit, text_edit): with open(file_path, 'r', encoding='utf-8') as file: content = file.read() current_text = text_edit.toPlainText() - if current_text: - text_edit.setText(f"{current_text}\n\n{content}") - else: - text_edit.setText(content) + text_edit.setText(current_text) except Exception as e: QMessageBox.warning(self, "Error", f"Failed to read file: {str(e)}") From e9b77d524b47752d1cbd7bf3a0af94abd5a0ec37 Mon Sep 17 00:00:00 2001 From: jpierzchala Date: Fri, 21 Mar 2025 14:12:44 +0100 Subject: [PATCH 3/6] Title: feat(transcription): add dynamic model selection and support for gpt-4o-transcribe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Body: • Update config_schema.yaml to include a new transcription model option, gpt-4o-transcribe. • Modify transcription.py to obtain the model from api_options instead of hardcoding "whisper-1". • Update the logging message to reflect the chosen model, ensuring transparency during API requests. These changes enable dynamic selection of OpenAI transcription models based on the configuration. --- src/config_schema.yaml | 1 + src/transcription.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/config_schema.yaml b/src/config_schema.yaml index ad7900c0..58decd3d 100644 --- a/src/config_schema.yaml +++ b/src/config_schema.yaml @@ -41,6 +41,7 @@ model_options: - whisper-large-v3-turbo - distil-whisper-large-v3-en - whisper-large-v3 + - gpt-4o-transcribe openai_transcription_api_key: value: null type: str diff --git a/src/transcription.py b/src/transcription.py index 6df8b130..fb16d885 100644 --- a/src/transcription.py +++ b/src/transcription.py @@ -307,12 +307,14 @@ def transcribe_with_openai(audio_data, api_options): sf.write(byte_io, audio_data, 16000, format='wav') byte_io.seek(0) + model = api_options['model'] + files = { 'file': ('audio.wav', byte_io, 'audio/wav'), - 'model': (None, 'whisper-1'), + 'model': (None, model), } - ConfigManager.console_print("Sending request to OpenAI API...") + ConfigManager.console_print(f"Sending request to OpenAI API using {model}...") response = requests.post( f"{base_url}/audio/transcriptions", headers=headers, From ba8b791193b8e085d17a6540b550c85d602a2359 Mon Sep 17 00:00:00 2001 From: Lord-Memester <71044282+Lord-Memester@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:38:11 -0500 Subject: [PATCH 4/6] fixed Ollama post processing The program was looking for an API key that Ollama doesn't have since it's local. I fixed it. I have no experience writing Python code, so this might break something else but for the time being it fixes the issue I had. (For example, this description was spellchecked by `airat/karen-the-editor-v2-strict:latest`!) --- .gitignore | 3 ++- src/llm_processor.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9baee880..39252f08 100644 --- a/.gitignore +++ b/.gitignore @@ -139,4 +139,5 @@ config.yaml *.keyring *.cfg *.db -/keyring/* \ No newline at end of file +/keyring/* +dictationRunner.bat diff --git a/src/llm_processor.py b/src/llm_processor.py index c898ab92..e07933b4 100644 --- a/src/llm_processor.py +++ b/src/llm_processor.py @@ -40,11 +40,11 @@ def __init__(self, api_type=None): self.api_key = KeyringManager.get_api_key("gemini") ConfigManager.console_print("Using Gemini API") elif self.api_type == 'ollama': + self.api_key = 'MemeWasHere' ConfigManager.console_print("Using local Ollama installation") elif self.api_type == 'groq': self.api_key = KeyringManager.get_api_key("groq") ConfigManager.console_print("Using Groq API") - if not self.api_key and self.api_type != 'ollama': ConfigManager.console_print(f"Warning: No API key found for {self.api_type}") @@ -285,7 +285,7 @@ def _process_ollama(self, text: str, system_message: str, model: str) -> str: if hasattr(models_response, 'models'): available_models = [] for model_info in models_response.models: - model_name = getattr(model_info, 'model', '').replace(':latest', '') + model_name = getattr(model_info, 'model', '')#.replace(':latest', '') # Commented out because every other program that looks for Ollama models that I've used keeps the ":latest" intact in the model name. Also, why change the raw output when you simply don't need to? ~ Meme details = getattr(model_info, 'details', None) available_models.append(model_name) From 7842e114c6948f6198b9f480cd1b4b653c19fae3 Mon Sep 17 00:00:00 2001 From: Meme <71044282+Lord-Memester@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:54:52 -0500 Subject: [PATCH 5/6] Update llm_processor.py changed ollama api token to something less gaudy --- src/llm_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llm_processor.py b/src/llm_processor.py index e07933b4..6230ecb2 100644 --- a/src/llm_processor.py +++ b/src/llm_processor.py @@ -40,7 +40,7 @@ def __init__(self, api_type=None): self.api_key = KeyringManager.get_api_key("gemini") ConfigManager.console_print("Using Gemini API") elif self.api_type == 'ollama': - self.api_key = 'MemeWasHere' + self.api_key = 'null' ConfigManager.console_print("Using local Ollama installation") elif self.api_type == 'groq': self.api_key = KeyringManager.get_api_key("groq") From 7f2e72fccb188f10ba6b7aa653822778532ce434 Mon Sep 17 00:00:00 2001 From: Meme <71044282+Lord-Memester@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:49:49 -0500 Subject: [PATCH 6/6] Update llm_processor.py modified as per request --- src/llm_processor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llm_processor.py b/src/llm_processor.py index 6230ecb2..81e932c4 100644 --- a/src/llm_processor.py +++ b/src/llm_processor.py @@ -285,7 +285,7 @@ def _process_ollama(self, text: str, system_message: str, model: str) -> str: if hasattr(models_response, 'models'): available_models = [] for model_info in models_response.models: - model_name = getattr(model_info, 'model', '')#.replace(':latest', '') # Commented out because every other program that looks for Ollama models that I've used keeps the ":latest" intact in the model name. Also, why change the raw output when you simply don't need to? ~ Meme + model_name = getattr(model_info, 'model', '').replace(':latest', '') details = getattr(model_info, 'details', None) available_models.append(model_name) @@ -473,4 +473,4 @@ def get_available_models(self, api_type): except Exception as e: ConfigManager.console_print(f"Error fetching {api_type} models: {str(e)}") - return [] \ No newline at end of file + return []