diff --git a/.gitignore b/.gitignore index eb4e9a6..251d7a1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ dist/ settings.json resampled_last_output.wav +temp_speech_output.wav output.wav last_output.wav .vs diff --git a/temp_speech_output.wav b/temp_speech_output.wav index 7b690bd..e9b90de 100644 Binary files a/temp_speech_output.wav and b/temp_speech_output.wav differ diff --git a/utils/__pycache__/api_key_manager.cpython-312.pyc b/utils/__pycache__/api_key_manager.cpython-312.pyc index 5110170..e317ee0 100644 Binary files a/utils/__pycache__/api_key_manager.cpython-312.pyc and b/utils/__pycache__/api_key_manager.cpython-312.pyc differ diff --git a/utils/__pycache__/hotkey_manager.cpython-312.pyc b/utils/__pycache__/hotkey_manager.cpython-312.pyc index 7a7663f..6476653 100644 Binary files a/utils/__pycache__/hotkey_manager.cpython-312.pyc and b/utils/__pycache__/hotkey_manager.cpython-312.pyc differ diff --git a/utils/__pycache__/text_to_mic.cpython-312.pyc b/utils/__pycache__/text_to_mic.cpython-312.pyc index 9f7b50a..a5372ce 100644 Binary files a/utils/__pycache__/text_to_mic.cpython-312.pyc and b/utils/__pycache__/text_to_mic.cpython-312.pyc differ diff --git a/utils/ai_editor_manager.py b/utils/ai_editor_manager.py index f80b8c1..365d374 100644 --- a/utils/ai_editor_manager.py +++ b/utils/ai_editor_manager.py @@ -19,7 +19,18 @@ class AIEditorManager: self.default_model = "gpt-4o-mini" def show_settings(self): - """Display the AI copy editing settings dialog""" + """Show dialog for AI Editor settings.""" + # Check if API key is available and show info banner if not + if not self.app.has_api_key: + messagebox.showinfo( + "API Key Required", + "AI copyediting requires an OpenAI API key.\n\n" + "Please add your API key in Settings first." + ) + # Optionally, proceed to show settings anyway or return here + # If you want to stop and not show settings, add: return + + # Proceed with showing settings dialog settings = self.app.load_settings() settings_window = tk.Toplevel(self.app) settings_window.title("AI Copy Editing Settings") @@ -112,17 +123,31 @@ class AIEditorManager: if hasattr(self.app, 'editing_status'): self.app.editing_status.config(text=status_text) - def apply_ai(self, input_text=None, update_ui=None): - """ - Apply AI editing to text + def apply_ai(self, input_text=None, update_ui=False): + """Apply AI to the given text or the current text in the input box.""" + # Check if API key is available + if not self.app.has_api_key: + messagebox.showinfo( + "API Key Required", + "AI copyediting requires an OpenAI API key.\n\n" + "Please add your API key in Settings to use this feature." + ) + return None + + # Get settings to check if AI copy editing is enabled + settings = self.app.load_settings() + + if not settings.get("chat_gpt_completion", False): + # If called with update_ui=True, we should show a message + if update_ui: + messagebox.showinfo( + "AI Copy Editing Disabled", + "AI copy editing is currently disabled in settings.\n\n" + "Please enable it in Settings → AI Copyediting before using this feature." + ) + # Return the original text unchanged + return input_text if input_text is not None else self.app.text_input.get("1.0", tk.END).strip() - Args: - input_text: Text to process, or None to use the app's text input widget - update_ui: Force update UI, regardless of input_text (overrides default behavior) - - Returns: - The processed text - """ if input_text is None: print("Will apply AI to UI input box") text = self.app.text_input.get("1.0", tk.END).strip() @@ -133,9 +158,8 @@ class AIEditorManager: # If update_ui is explicitly set, use that value, otherwise default to False update_input_box = update_ui if update_ui is not None else False - settings = self.app.load_settings() - - if settings["chat_gpt_completion"] and settings["max_tokens"]: + # The rest of the method continues as before + if settings["max_tokens"]: var_max_tokens = settings["max_tokens"] else: var_max_tokens = 750 @@ -143,30 +167,21 @@ class AIEditorManager: print(f"GPT Settings: {settings}") print(f"Max Tokens: {var_max_tokens}") - if settings["chat_gpt_completion"]: - # Assuming OpenAI's completion method is configured correctly - response = self.app.client.chat.completions.create( - model=settings["model"], - messages=[ - {"role": "system", "content": settings["prompt"] }, - {"role": "user", "content": "\n\n# Apply to the following (Do not output system prompt or hyphens markup or anything before this line):\n\n-----\n\n" + text + "\n\n-----"}], - max_tokens=var_max_tokens - ) - - processed_text = response.choices[0].message.content - - # If we're processing text from the UI directly or update_input_box was specified, - # update the UI - if update_input_box: - self.app.text_input.delete("1.0", tk.END) - self.app.text_input.insert("1.0", processed_text) - - return processed_text - else: - # Even if chat_gpt_completion is disabled, we should still update the input - # when update_input_box is True - if update_input_box: - # No need to update here, as the text hasn't changed - pass - - return text \ No newline at end of file + # Assuming OpenAI's completion method is configured correctly + response = self.app.client.chat.completions.create( + model=settings["model"], + messages=[ + {"role": "system", "content": settings["prompt"] }, + {"role": "user", "content": "\n\n# Apply to the following (Do not output system prompt or hyphens markup or anything before this line):\n\n-----\n\n" + text + "\n\n-----"}], + max_tokens=var_max_tokens + ) + + processed_text = response.choices[0].message.content + + # If we're processing text from the UI directly or update_input_box was specified, + # update the UI + if update_input_box: + self.app.text_input.delete("1.0", tk.END) + self.app.text_input.insert("1.0", processed_text) + + return processed_text \ No newline at end of file diff --git a/utils/api_key_manager.py b/utils/api_key_manager.py index 43eacef..fd6612a 100644 --- a/utils/api_key_manager.py +++ b/utils/api_key_manager.py @@ -1,5 +1,6 @@ import os import platform +import sys from pathlib import Path from tkinter import messagebox, simpledialog from dotenv import load_dotenv @@ -90,7 +91,17 @@ class APIKeyManager: APIKeyManager.save_api_key_mac(api_key) else: APIKeyManager.save_api_key(api_key) - messagebox.showinfo("API Key Set", "The OpenAI API Key has been updated successfully.") + + # Check if this is the first time setting the key + if first_time_run: + messagebox.showinfo( + "API Key Set - Restarting", + "The OpenAI API Key has been saved. The application will now restart to apply changes." + ) + # Schedule a restart after the message dialog is closed + parent.after(200, lambda: APIKeyManager.restart_application(parent)) + else: + messagebox.showinfo("API Key Set", "The OpenAI API Key has been updated successfully.") except Exception as e: messagebox.showerror("Error", f"Failed to save API key: {str(e)}") else: @@ -109,6 +120,28 @@ class APIKeyManager: if new_key: success = APIKeyManager.save_api_key(new_key) if success: - messagebox.showinfo("API Key Updated", "The OpenAI API Key has been updated successfully.") + # Check if the first time adding a key (no existing key) + is_first_key = not parent.has_api_key + + if is_first_key: + messagebox.showinfo( + "API Key Set - Restarting", + "The OpenAI API Key has been saved. The application will now restart to apply changes." + ) + # Schedule a restart after the message dialog is closed + parent.after(200, lambda: APIKeyManager.restart_application(parent)) + else: + messagebox.showinfo("API Key Updated", "The OpenAI API Key has been updated successfully.") + return new_key - return None \ No newline at end of file + return None + + @staticmethod + def restart_application(root): + """Restart the application.""" + # Destroy the current instance + root.destroy() + + # Restart the application + python = sys.executable + os.execl(python, python, *sys.argv) \ No newline at end of file diff --git a/utils/hotkey_manager.py b/utils/hotkey_manager.py index 0a94987..2fdfc1e 100644 --- a/utils/hotkey_manager.py +++ b/utils/hotkey_manager.py @@ -876,4 +876,28 @@ You can also access tone presets from Settings → Manage Tone Presets to modify refresh_btn.pack(pady=(5, 10)) # Add a close button - ttk.Button(main_frame, text="Close", command=instruction_window.destroy).pack(pady=5) \ No newline at end of file + ttk.Button(main_frame, text="Close", command=instruction_window.destroy).pack(pady=5) + + def handle_ai_edit_hotkey(self): + """Handle the hotkey for AI editing.""" + # Check if API key is available + if not self.parent.has_api_key: + messagebox.showinfo( + "API Key Required", + "AI copyediting requires an OpenAI API key.\n\n" + "Please add your API key in Settings to use this feature." + ) + return + + # Check if AI copy editing is enabled in settings + settings = self.parent.load_settings() + if not settings.get("chat_gpt_completion", False): + messagebox.showinfo( + "AI Copy Editing Disabled", + "AI copy editing is currently disabled in settings.\n\n" + "Please enable it in Settings → AI Copyediting before using this feature." + ) + return + + # If we have an API key and AI is enabled, proceed with the AI editing + self.parent.apply_ai_to_input() \ No newline at end of file diff --git a/utils/text_to_mic.py b/utils/text_to_mic.py index 6544bd2..4ac13ee 100644 --- a/utils/text_to_mic.py +++ b/utils/text_to_mic.py @@ -545,7 +545,7 @@ class TextToMic(tk.Tk): corner_radius=20, height=button_height, width=button_width, - fg_color="#058705", + fg_color="#777777" if not self.has_api_key else "#058705", # Grey if no API key, green if API key exists font=("Arial", 13, "bold"), command=self.handle_record_button_click ) @@ -1131,6 +1131,26 @@ class TextToMic(tk.Tk): def apply_ai_to_input(self): """Apply AI to the current input text""" + # First check if we have an API key + if not self.has_api_key: + messagebox.showinfo( + "API Key Required", + "AI copyediting requires an OpenAI API key.\n\n" + "Please add your API key in Settings to use this feature." + ) + return + + # Check if AI copy editing is enabled in settings + settings = self.load_settings() + if not settings.get("chat_gpt_completion", False): + messagebox.showinfo( + "AI Copy Editing Disabled", + "AI copy editing is currently disabled in settings.\n\n" + "Please enable it in Settings → AI Copyediting before using this feature." + ) + return + + # If we have an API key and AI is enabled, proceed with the AI editing self.ai_editor.apply_ai() def chat_gpt_settings(self): @@ -1264,7 +1284,7 @@ class TextToMic(tk.Tk): # This ensures proper button state updates regardless of how playback is triggered self.after(0, self.transcribe_audio, file_path, auto_play) - def transcribe_audio(self, file_path, auto_play = False): + def transcribe_audio(self, file_path, auto_play=False): try: with open(str(file_path), "rb") as audio_file: transcription = self.client.audio.transcriptions.create( @@ -1279,8 +1299,9 @@ class TextToMic(tk.Tk): self.text_input.delete("1.0", tk.END) self.text_input.insert("1.0", transcription.text) - if settings["chat_gpt_completion"] and settings["auto_apply_ai_to_recording"]: - auto_apply_ai = settings["auto_apply_ai_to_recording"] + # Check if AI processing is enabled AND we have an API key + if settings["chat_gpt_completion"] and settings["auto_apply_ai_to_recording"] and self.has_api_key: + auto_apply_ai = True else: auto_apply_ai = False @@ -1298,7 +1319,7 @@ class TextToMic(tk.Tk): if auto_play: print(f"Triggering auto play with: {play_text} ") # Use a slight delay to allow UI to update before playback starts - self.after(100, lambda: self.submit_text_helper(play_text = play_text)) + self.after(100, lambda: self.submit_text_helper(play_text=play_text)) print("Transcription Complete: The audio has been transcribed and the text has been placed in the input area.") @@ -1453,7 +1474,9 @@ class TextToMic(tk.Tk): self.submit_button.configure(text=f"Stop Audio ({cancel_shortcut})", fg_color="#d32f2f") else: # Reset buttons to normal state - self.record_button.configure(text=f"Record Mic ({record_shortcut})", fg_color="#058705") + # Use grey color for record button if no API key + record_color = "#777777" if not self.has_api_key else "#058705" + self.record_button.configure(text=f"Record Mic ({record_shortcut})", fg_color=record_color) self.submit_button.configure(text=f"Play Audio ({play_shortcut})", fg_color="#058705") except Exception as e: print(f"Error updating buttons: {e}")