diff --git a/Windows_and_Linux/README.md b/Windows_and_Linux/README.md
new file mode 100644
index 0000000..3946f55
--- /dev/null
+++ b/Windows_and_Linux/README.md
@@ -0,0 +1,96 @@
+## ⚠ refer to main readme: still experimental ⚠
+
+## Linux
+
+### X11
+- Captures text via synthetic Ctrl+C using `pynput` + `pyperclip`.
+
+### Wayland
+- Captures text via `wl-paste --primary` (requires `wl-clipboard`).
+- Retrieves active window title from:
+ 1. **wlroots-based** (Sway, Hyprland, Labwc): `wlrctl toplevel list --json`
+ 2. **KDE Plasma**: `kwin5 activewindow` + `kwin5 windowtitle " + \
- _("Writing Tools is a free & lightweight tool that helps you improve your writing with AI, similar to Apple's new Apple Intelligence feature. It works with an extensive range of AI LLMs, both online and locally run.") + \
- """
+ title_label.setStyleSheet(
+ f"font-size: 24px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};"
+ )
+ content_layout.addWidget(
+ title_label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter
+ )
+
+ about_text = (
+ " "
+ + _(
+ "Writing Tools is a free & lightweight tool that helps you improve your writing with AI, similar to Apple's new Apple Intelligence feature. It works with an extensive range of AI LLMs, both online and locally run."
+ )
+ + """
""" + \
- "" + _("Created with care by Jesai, a high school student.") +" """
+ + ""
+ + _("Created with care by Jesai, a high school student.")
+ + "
- ⭐ """ + \
- _("Writing Tools would not be where it is today without its amazing contributors") + ":
" + \
- _("Feel free to check out my other AI app") + ", Bliss AI. " + _("It's a novel AI tutor that's free on the Google Play Store :)") + "
" + \
- "" + _("Contact me") +": jesaitarun@gmail.com
" + \
- """
"
+ + _("Feel free to check out my other AI app")
+ + ', Bliss AI. '
+ + _("It's a novel AI tutor that's free on the Google Play Store :)")
+ + "
"
+ + ""
+ + _("Contact me")
+ + ": jesaitarun@gmail.com
"
+ + """
" + \
- "1. momokrono:
" + \
- _("Added Linux support, switched to the pynput API to improve Windows stability. Added Ollama API support, core logic for customizable buttons, and localization. Fixed misc. bugs and added graceful termination support by handling SIGINT signal.") + "
" + \
- "2. Cameron Redmore (CameronRedmore):
" + \
- _("Extensively refactored Writing Tools and added OpenAI Compatible API support, streamed responses, and the text generation mode when no text is selected.") + "
" + \
- '3. Soszust40 (Soszust40):
' + \
- _('Helped add dark mode, the plain theme, tray menu fixes, and UI improvements.') + '
' + \
- '4. Alok Saboo (arsaboo):
' + \
- _('Helped improve the reliability of text selection.') + '
' + \
- '5. raghavdhingra24:
' + \
- _('Made the rounded corners anti-aliased & prettier.')+'
' + \
- '6. ErrorCatDev:
' + \
- _('Significantly improved the About window, making it scrollable and cleaning things up. Also improved our .gitignore & requirements.txt.') + '
' + \
- '7. Vadim Karpenko:
' + \
- _('Helped add the start-on-boot setting.')+ "
" + \
- 'If you have a Mac, be sure to check out the Writing Tools macOS port by Arya Mirsepasi!
' + \
- """
"
+ + '1. momokrono:
'
+ + _(
+ "Added Linux support, switched to the pynput API to improve Windows stability. Added Ollama API support, core logic for customizable buttons, and localization. Fixed misc. bugs and added graceful termination support by handling SIGINT signal."
+ )
+ + "
"
+ + '2. Cameron Redmore (CameronRedmore):
'
+ + _(
+ "Extensively refactored Writing Tools and added OpenAI Compatible API support, streamed responses, and the text generation mode when no text is selected."
+ )
+ + "
"
+ + '3. Soszust40 (Soszust40):
'
+ + _(
+ "Helped add dark mode, the plain theme, tray menu fixes, and UI improvements."
+ )
+ + "
"
+ + '4. Alok Saboo (arsaboo):
'
+ + _("Helped improve the reliability of text selection.")
+ + "
"
+ + '5. raghavdhingra24:
'
+ + _("Made the rounded corners anti-aliased & prettier.")
+ + "
"
+ + '6. ErrorCatDev:
'
+ + _(
+ "Significantly improved the About window, making it scrollable and cleaning things up. Also improved our .gitignore & requirements.txt."
+ )
+ + "
"
+ + '7. Vadim Karpenko:
'
+ + _("Helped add the start-on-boot setting.")
+ + "
"
+ + 'If you have a Mac, be sure to check out the Writing Tools macOS port by Arya Mirsepasi!
'
+ + """
Version: 7.0 (Codename: Impeccably Improved)
""" + ) about_label = QtWidgets.QLabel(about_text) - about_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + about_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) about_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) about_label.setWordWrap(True) about_label.setOpenExternalLinks(True) # Allow opening hyperlinks @@ -94,7 +139,7 @@ def init_ui(self): content_layout.addWidget(scroll_area) # Add "Check for updates" button - update_button = QtWidgets.QPushButton('Check for updates') + update_button = QtWidgets.QPushButton("Check for updates") update_button.setStyleSheet(""" QPushButton { background-color: #4CAF50; @@ -121,4 +166,4 @@ def original_app(self): """ Open the original app GitHub page. """ - webbrowser.open("https://github.com/TheJayTea/WritingTools") \ No newline at end of file + webbrowser.open("https://github.com/TheJayTea/WritingTools") diff --git a/Windows_and_Linux/ui/AutostartManager.py b/Windows_and_Linux/ui/AutostartManager.py index 3c96c84..7e62a06 100644 --- a/Windows_and_Linux/ui/AutostartManager.py +++ b/Windows_and_Linux/ui/AutostartManager.py @@ -4,18 +4,19 @@ if sys.platform.startswith("win32"): import winreg + class AutostartManager: """ Manages the autostart functionality for Writing Tools. Handles setting/removing autostart registry entries on Windows. """ - + @staticmethod def is_compiled(): """ Check if we're running from a compiled exe or source. """ - return hasattr(sys, 'frozen') and hasattr(sys, '_MEIPASS') + return hasattr(sys, "frozen") and hasattr(sys, "_MEIPASS") @staticmethod def get_startup_path(): @@ -23,22 +24,22 @@ def get_startup_path(): Get the path that should be used for autostart. Returns None if running from source or on non-Windows. """ - if not sys.platform.startswith('win32'): + if not sys.platform.startswith("win32"): return None - + if not AutostartManager.is_compiled(): return None - + return sys.executable @staticmethod def set_autostart(enable: bool) -> bool: """ Enable or disable autostart for Writing Tools. - + Args: enable: True to enable autostart, False to disable - + Returns: bool: True if operation succeeded, False if failed or unsupported """ @@ -48,31 +49,34 @@ def set_autostart(enable: bool) -> bool: return False key_path = r"Software\Microsoft\Windows\CurrentVersion\Run" - + try: if enable: # Open/create key and set value - key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, - winreg.KEY_WRITE) - winreg.SetValueEx(key, "WritingTools", 0, winreg.REG_SZ, - startup_path) + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_WRITE + ) + winreg.SetValueEx( + key, "WritingTools", 0, winreg.REG_SZ, startup_path + ) else: # Open key and delete value if it exists - key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, - winreg.KEY_WRITE) + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_WRITE + ) try: winreg.DeleteValue(key, "WritingTools") except WindowsError: # Value doesn't exist, that's fine pass - + winreg.CloseKey(key) return True - + except WindowsError as e: logging.error(f"Failed to modify autostart registry: {e}") return False - + except Exception as e: logging.error(f"Error managing autostart: {e}") return False @@ -81,7 +85,7 @@ def set_autostart(enable: bool) -> bool: def check_autostart() -> bool: """ Check if Writing Tools is set to start automatically. - + Returns: bool: True if autostart is enabled, False if disabled or unsupported """ @@ -91,19 +95,22 @@ def check_autostart() -> bool: return False try: - key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - 0, winreg.KEY_READ) + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Run", + 0, + winreg.KEY_READ, + ) value, _ = winreg.QueryValueEx(key, "WritingTools") winreg.CloseKey(key) - + # Check if the stored path matches our current exe return value.lower() == startup_path.lower() - + except WindowsError: # Key or value doesn't exist return False - + except Exception as e: logging.error(f"Error checking autostart status: {e}") - return False \ No newline at end of file + return False diff --git a/Windows_and_Linux/ui/CustomPopupWindow.py b/Windows_and_Linux/ui/CustomPopupWindow.py index 44e8593..a003656 100644 --- a/Windows_and_Linux/ui/CustomPopupWindow.py +++ b/Windows_and_Linux/ui/CustomPopupWindow.py @@ -82,54 +82,66 @@ } }""" + class ButtonEditDialog(QDialog): """ Dialog for editing or creating a button's properties (name/title, system instruction, open_in_window, etc.). """ + def __init__(self, parent=None, button_data=None, title="Edit Button"): super().__init__(parent) - self.button_data = button_data if button_data else { - "prefix": "Make this change to the following text:\n\n", - "instruction": "", - "icon": "icons/magnifying-glass", - "open_in_window": False - } + self.button_data = ( + button_data + if button_data + else { + "prefix": "Make this change to the following text:\n\n", + "instruction": "", + "icon": "icons/magnifying-glass", + "open_in_window": False, + } + ) self.setWindowTitle(title) self.init_ui() - + def init_ui(self): layout = QVBoxLayout(self) - + # Name name_label = QLabel("Button Name:") - name_label.setStyleSheet(f"color: {'#fff' if colorMode == 'dark' else '#333'}; font-weight: bold;") + name_label.setStyleSheet( + f"color: {'#fff' if colorMode == 'dark' else '#333'}; font-weight: bold;" + ) self.name_input = QLineEdit() self.name_input.setStyleSheet(f""" QLineEdit {{ padding: 8px; - border: 1px solid {'#777' if colorMode == 'dark' else '#ccc'}; + border: 1px solid {"#777" if colorMode == "dark" else "#ccc"}; border-radius: 8px; - background-color: {'#333' if colorMode == 'dark' else 'white'}; - color: {'#fff' if colorMode == 'dark' else '#000'}; + background-color: {"#333" if colorMode == "dark" else "white"}; + color: {"#fff" if colorMode == "dark" else "#000"}; }} """) if "name" in self.button_data: self.name_input.setText(self.button_data["name"]) layout.addWidget(name_label) layout.addWidget(self.name_input) - + # Instruction (changed to a multiline QPlainTextEdit) - instruction_label = QLabel("What should your AI do with your selected text? (System Instruction)") - instruction_label.setStyleSheet(f"color: {'#fff' if colorMode == 'dark' else '#333'}; font-weight: bold;") + instruction_label = QLabel( + "What should your AI do with your selected text? (System Instruction)" + ) + instruction_label.setStyleSheet( + f"color: {'#fff' if colorMode == 'dark' else '#333'}; font-weight: bold;" + ) self.instruction_input = QPlainTextEdit() self.instruction_input.setStyleSheet(f""" QPlainTextEdit {{ padding: 8px; - border: 1px solid {'#777' if colorMode == 'dark' else '#ccc'}; + border: 1px solid {"#777" if colorMode == "dark" else "#ccc"}; border-radius: 8px; - background-color: {'#333' if colorMode == 'dark' else 'white'}; - color: {'#fff' if colorMode == 'dark' else '#000'}; + background-color: {"#333" if colorMode == "dark" else "white"}; + color: {"#fff" if colorMode == "dark" else "#000"}; }} """) self.instruction_input.setPlainText(self.button_data.get("instruction", "")) @@ -146,25 +158,27 @@ def init_ui(self): - Analyse potential biases in this news article.""") layout.addWidget(instruction_label) layout.addWidget(self.instruction_input) - + # open_in_window display_label = QLabel("How should your AI response be shown?") - display_label.setStyleSheet(f"color: {'#fff' if colorMode == 'dark' else '#333'}; font-weight: bold;") + display_label.setStyleSheet( + f"color: {'#fff' if colorMode == 'dark' else '#333'}; font-weight: bold;" + ) layout.addWidget(display_label) - + radio_layout = QHBoxLayout() self.replace_radio = QRadioButton("Replace the selected text") self.window_radio = QRadioButton("In a pop-up window (with follow-up support)") for r in (self.replace_radio, self.window_radio): r.setStyleSheet(f"color: {'#fff' if colorMode == 'dark' else '#333'};") - + self.replace_radio.setChecked(not self.button_data.get("open_in_window", False)) self.window_radio.setChecked(self.button_data.get("open_in_window", False)) - + radio_layout.addWidget(self.replace_radio) radio_layout.addWidget(self.window_radio) layout.addLayout(radio_layout) - + # OK & Cancel btn_layout = QHBoxLayout() ok_button = QPushButton("OK") @@ -172,27 +186,27 @@ def init_ui(self): for btn in (ok_button, cancel_button): btn.setStyleSheet(f""" QPushButton {{ - background-color: {'#444' if colorMode == 'dark' else '#f0f0f0'}; - color: {'#fff' if colorMode == 'dark' else '#000'}; - border: 1px solid {'#666' if colorMode == 'dark' else '#ccc'}; + background-color: {"#444" if colorMode == "dark" else "#f0f0f0"}; + color: {"#fff" if colorMode == "dark" else "#000"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; border-radius: 5px; padding: 8px; min-width: 100px; }} QPushButton:hover {{ - background-color: {'#555' if colorMode == 'dark' else '#e0e0e0'}; + background-color: {"#555" if colorMode == "dark" else "#e0e0e0"}; }} """) btn_layout.addWidget(ok_button) btn_layout.addWidget(cancel_button) layout.addLayout(btn_layout) - + ok_button.clicked.connect(self.accept) cancel_button.clicked.connect(self.reject) - + self.setStyleSheet(f""" QDialog {{ - background-color: {'#222' if colorMode == 'dark' else '#f5f5f5'}; + background-color: {"#222" if colorMode == "dark" else "#f5f5f5"}; border-radius: 10px; }} """) @@ -204,9 +218,10 @@ def get_button_data(self): # Retrieve multiline text "instruction": self.instruction_input.toPlainText(), "icon": "icons/custom", - "open_in_window": self.window_radio.isChecked() + "open_in_window": self.window_radio.isChecked(), } + class DraggableButton(QtWidgets.QPushButton): def __init__(self, parent_popup, key, text): super().__init__(text, parent_popup) @@ -230,16 +245,16 @@ def __init__(self, parent_popup, key, text): # Define base style using the dynamic property instead of the :hover pseudo-class self.base_style = f""" QPushButton {{ - background-color: {"#444" if colorMode=="dark" else "white"}; - border: 1px solid {"#666" if colorMode=="dark" else "#ccc"}; + background-color: {"#444" if colorMode == "dark" else "white"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; border-radius: 8px; padding: 10px; font-size: 14px; text-align: left; - color: {"#fff" if colorMode=="dark" else "#000"}; + color: {"#fff" if colorMode == "dark" else "#000"}; }} QPushButton[hover="true"] {{ - background-color: {"#555" if colorMode=="dark" else "#f0f0f0"}; + background-color: {"#555" if colorMode == "dark" else "#f0f0f0"}; }} """ self.setStyleSheet(self.base_style) @@ -267,7 +282,7 @@ def mousePressEvent(self, event): event.accept() return super().mousePressEvent(event) - + def mouseMoveEvent(self, event): if not (event.buttons() & QtCore.Qt.LeftButton) or not self.drag_start_position: return @@ -292,13 +307,18 @@ def mouseMoveEvent(self, event): logging.debug(f"Drag completed with action: {drop_action}") def dragEnterEvent(self, event): - if self.popup.edit_mode and event.mimeData().hasFormat("application/x-button-index"): + if self.popup.edit_mode and event.mimeData().hasFormat( + "application/x-button-index" + ): event.acceptProposedAction() - self.setStyleSheet(self.base_style + """ + self.setStyleSheet( + self.base_style + + """ QPushButton { border: 2px dashed #666; } - """) + """ + ) else: event.ignore() @@ -307,11 +327,15 @@ def dragLeaveEvent(self, event): event.accept() def dropEvent(self, event): - if not self.popup.edit_mode or not event.mimeData().hasFormat("application/x-button-index"): + if not self.popup.edit_mode or not event.mimeData().hasFormat( + "application/x-button-index" + ): event.ignore() return - source_idx = int(event.mimeData().data("application/x-button-index").data().decode()) + source_idx = int( + event.mimeData().data("application/x-button-index").data().decode() + ) target_idx = self.popup.button_widgets.index(self) if source_idx != target_idx: @@ -329,6 +353,7 @@ def resizeEvent(self, event): if self.icon_container: self.icon_container.setGeometry(0, 0, self.width(), self.height()) + class CustomPopupWindow(QtWidgets.QWidget): def __init__(self, app, selected_text): super().__init__() @@ -336,41 +361,43 @@ def __init__(self, app, selected_text): self.selected_text = selected_text self.edit_mode = False self.has_text = bool(selected_text.strip()) - + self.drag_label = None self.edit_button = None self.reset_button = None self.close_button = None self.custom_input = None self.input_area = None - + self.button_widgets = [] - logging.debug('Initializing CustomPopupWindow') + logging.debug("Initializing CustomPopupWindow") self.init_ui() def init_ui(self): - logging.debug('Setting up CustomPopupWindow UI') - self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint) + logging.debug("Setting up CustomPopupWindow UI") + self.setWindowFlags( + QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint + ) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setWindowTitle("Writing Tools") - + main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(0,0,0,0) - + main_layout.setContentsMargins(0, 0, 0, 0) + self.background = ThemeBackground( - self, - self.app.config.get('theme','gradient'), + self, + self.app.config.get("theme", "gradient"), is_popup=True, - border_radius=10 + border_radius=10, ) main_layout.addWidget(self.background) - + content_layout = QtWidgets.QVBoxLayout(self.background) # Margin Control content_layout.setContentsMargins(10, 4, 10, 10) content_layout.setSpacing(10) - + # TOP BAR LAYOUT & STYLE top_bar = QHBoxLayout() top_bar.setContentsMargins(0, 0, 0, 0) @@ -378,9 +405,11 @@ def init_ui(self): # The "Edit"/"Done" button (left), same exact size as close button self.edit_button = QPushButton() - pencil_icon = os.path.join(os.path.dirname(sys.argv[0]), - 'icons', - 'pencil' + ('_dark' if colorMode=='dark' else '_light') + '.png') + pencil_icon = os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + "pencil" + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) if os.path.exists(pencil_icon): self.edit_button.setIcon(QtGui.QIcon(pencil_icon)) # Reduced size to 24x24 to shrink top bar @@ -394,7 +423,7 @@ def init_ui(self): margin-top: 3px; }} QPushButton:hover {{ - background-color: {'#333' if colorMode=='dark' else '#ebebeb'}; + background-color: {"#333" if colorMode == "dark" else "#ebebeb"}; }} """) self.edit_button.clicked.connect(self.toggle_edit_mode) @@ -403,7 +432,7 @@ def init_ui(self): # The label "Drag to rearrange" (BOLD as requested) self.drag_label = QLabel("Drag to rearrange") self.drag_label.setStyleSheet(f""" - color: {'#fff' if colorMode=='dark' else '#333'}; + color: {"#fff" if colorMode == "dark" else "#333"}; font-size: 14px; font-weight: bold; /* <--- BOLD TEXT */ """) @@ -413,8 +442,11 @@ def init_ui(self): # The "Reset" button (edit-mode only) - also 24x24 self.reset_button = QPushButton() - reset_icon_path = os.path.join(os.path.dirname(sys.argv[0]), 'icons', - 'restore' + ('_dark' if colorMode=='dark' else '_light') + '.png') + reset_icon_path = os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + "restore" + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) if os.path.exists(reset_icon_path): self.reset_button.setIcon(QtGui.QIcon(reset_icon_path)) self.reset_button.setText("") @@ -427,7 +459,7 @@ def init_ui(self): padding: 0px; }} QPushButton:hover {{ - background-color: {'#333' if colorMode=='dark' else '#ebebeb'}; + background-color: {"#333" if colorMode == "dark" else "#ebebeb"}; }} """) self.reset_button.clicked.connect(self.on_reset_clicked) @@ -440,7 +472,7 @@ def init_ui(self): self.close_button.setStyleSheet(f""" QPushButton {{ background-color: transparent; - color: {'#fff' if colorMode=='dark' else '#333'}; + color: {"#fff" if colorMode == "dark" else "#333"}; font-size: 20px; /* bigger text */ font-weight: bold; /* bold text */ border: none; @@ -448,57 +480,61 @@ def init_ui(self): padding: 0px; }} QPushButton:hover {{ - background-color: {'#333' if colorMode=='dark' else '#ebebeb'}; + background-color: {"#333" if colorMode == "dark" else "#ebebeb"}; }} """) self.close_button.clicked.connect(self.close) top_bar.addWidget(self.close_button, 0, Qt.AlignRight) content_layout.addLayout(top_bar) - # Input area (hidden in edit mode) self.input_area = QWidget() input_layout = QHBoxLayout(self.input_area) - input_layout.setContentsMargins(0,0,0,0) - + input_layout.setContentsMargins(0, 0, 0, 0) + self.custom_input = QLineEdit() - self.custom_input.setPlaceholderText(_("Describe your change...") if self.has_text else _("Ask your AI...")) + self.custom_input.setPlaceholderText( + _("Describe your change...") if self.has_text else _("Ask your AI...") + ) self.custom_input.setStyleSheet(f""" QLineEdit {{ padding: 8px; - border: 1px solid {'#777' if colorMode=='dark' else '#ccc'}; + border: 1px solid {"#777" if colorMode == "dark" else "#ccc"}; border-radius: 8px; - background-color: {'#333' if colorMode=='dark' else 'white'}; - color: {'#fff' if colorMode=='dark' else '#000'}; + background-color: {"#333" if colorMode == "dark" else "white"}; + color: {"#fff" if colorMode == "dark" else "#000"}; }} """) self.custom_input.returnPressed.connect(self.on_custom_change) input_layout.addWidget(self.custom_input) - + send_btn = QPushButton() - send_icon = os.path.join(os.path.dirname(sys.argv[0]), - 'icons', - 'send' + ('_dark' if colorMode=='dark' else '_light') + '.png') + send_icon = os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + "send" + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) if os.path.exists(send_icon): send_btn.setIcon(QtGui.QIcon(send_icon)) send_btn.setStyleSheet(f""" QPushButton {{ - background-color: {'#2e7d32' if colorMode=='dark' else '#4CAF50'}; + background-color: {"#2e7d32" if colorMode == "dark" else "#4CAF50"}; border: none; border-radius: 8px; padding: 5px; }} QPushButton:hover {{ - background-color: {'#1b5e20' if colorMode=='dark' else '#45a049'}; + background-color: {"#1b5e20" if colorMode == "dark" else "#45a049"}; }} """) - send_btn.setFixedSize(self.custom_input.sizeHint().height(), - self.custom_input.sizeHint().height()) + send_btn.setFixedSize( + self.custom_input.sizeHint().height(), self.custom_input.sizeHint().height() + ) send_btn.clicked.connect(self.on_custom_change) input_layout.addWidget(send_btn) - + content_layout.addWidget(self.input_area) - + if self.has_text: self.build_buttons_list() self.rebuild_grid_layout(content_layout) @@ -511,30 +547,32 @@ def init_ui(self): if self.app.config.get("update_available", False): update_label = QLabel() update_label.setOpenExternalLinks(True) - update_label.setText('There\'s an update! :D Download now.') + update_label.setText( + 'There\'s an update! :D Download now.' + ) update_label.setStyleSheet("margin-top: 10px;") content_layout.addWidget(update_label, alignment=QtCore.Qt.AlignCenter) - - logging.debug('CustomPopupWindow UI setup complete') + + logging.debug("CustomPopupWindow UI setup complete") self.installEventFilter(self) QtCore.QTimer.singleShot(250, lambda: self.custom_input.setFocus()) @staticmethod def load_options(): - options_path = os.path.join(os.path.dirname(sys.argv[0]), 'options.json') + options_path = os.path.join(os.path.dirname(sys.argv[0]), "options.json") if os.path.exists(options_path): - with open(options_path, 'r') as f: + with open(options_path, "r") as f: data = json.load(f) - logging.debug('Options loaded successfully') + logging.debug("Options loaded successfully") else: - logging.debug('Options file not found') + logging.debug("Options file not found") return data @staticmethod def save_options(options): - options_path = os.path.join(os.path.dirname(sys.argv[0]), 'options.json') - with open(options_path, 'w') as f: + options_path = os.path.join(os.path.dirname(sys.argv[0]), "options.json") + with open(options_path, "w") as f: json.dump(options, f, indent=2) def build_buttons_list(self): @@ -545,15 +583,17 @@ def build_buttons_list(self): self.button_widgets.clear() data = self.load_options() - for k,v in data.items(): - if k=="Custom": + for k, v in data.items(): + if k == "Custom": continue b = DraggableButton(self, k, k) - icon_path = os.path.join(os.path.dirname(sys.argv[0]), - v["icon"] + ('_dark' if colorMode=='dark' else '_light') + '.png') + icon_path = os.path.join( + os.path.dirname(sys.argv[0]), + v["icon"] + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) if os.path.exists(icon_path): b.setIcon(QtGui.QIcon(icon_path)) - + if not self.edit_mode: b.clicked.connect(partial(self.on_generic_instruction, k)) self.button_widgets.append(b) @@ -573,16 +613,19 @@ def rebuild_grid_layout(self, parent_layout=None): if w: grid.removeWidget(w) parent_layout.removeItem(grid) - elif (item.widget() and isinstance(item.widget(), QPushButton) - and item.widget().text() == "+ Add New"): + elif ( + item.widget() + and isinstance(item.widget(), QPushButton) + and item.widget().text() == "+ Add New" + ): item.widget().deleteLater() # Create new grid with fixed column width grid = QtWidgets.QGridLayout() - grid.setSpacing(10) + grid.setSpacing(10) grid.setColumnMinimumWidth(0, 120) grid.setColumnMinimumWidth(1, 120) - + # Add buttons to grid row = 0 col = 0 @@ -592,25 +635,25 @@ def rebuild_grid_layout(self, parent_layout=None): if col > 1: col = 0 row += 1 - + parent_layout.addLayout(grid) - + # Add New button (only in edit mode & only if we have text) if self.edit_mode and self.has_text: add_btn = QPushButton("+ Add New") add_btn.setStyleSheet(f""" QPushButton {{ - background-color: {'#333' if colorMode=='dark' else '#e0e0e0'}; - border: 1px solid {'#666' if colorMode=='dark' else '#ccc'}; + background-color: {"#333" if colorMode == "dark" else "#e0e0e0"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; border-radius: 8px; padding: 10px; font-size: 14px; text-align: center; - color: {'#fff' if colorMode=='dark' else '#000'}; + color: {"#fff" if colorMode == "dark" else "#000"}; margin-top: 10px; }} QPushButton:hover {{ - background-color: {'#444' if colorMode=='dark' else '#d0d0d0'}; + background-color: {"#444" if colorMode == "dark" else "#d0d0d0"}; }} """) add_btn.clicked.connect(self.add_new_button_clicked) @@ -618,17 +661,17 @@ def rebuild_grid_layout(self, parent_layout=None): def add_edit_delete_icons(self, btn): """Add edit/delete icons as overlays with proper spacing.""" - if hasattr(btn, 'icon_container') and btn.icon_container: + if hasattr(btn, "icon_container") and btn.icon_container: btn.icon_container.deleteLater() - + btn.icon_container = QtWidgets.QWidget(btn) btn.icon_container.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, False) - + btn.icon_container.setGeometry(0, 0, btn.width(), btn.height()) - + circle_style = f""" QPushButton {{ - background-color: {'#666' if colorMode=='dark' else '#999'}; + background-color: {"#666" if colorMode == "dark" else "#999"}; border-radius: 10px; min-width: 16px; min-height: 16px; @@ -638,39 +681,45 @@ def add_edit_delete_icons(self, btn): margin: 0px; }} QPushButton:hover {{ - background-color: {'#888' if colorMode=='dark' else '#bbb'}; + background-color: {"#888" if colorMode == "dark" else "#bbb"}; }} """ - + # Create edit icon (top-left) edit_btn = QPushButton(btn.icon_container) edit_btn.setGeometry(3, 3, 16, 16) - pencil_icon = os.path.join(os.path.dirname(sys.argv[0]), - 'icons', 'pencil' + ('_dark' if colorMode=='dark' else '_light') + '.png') + pencil_icon = os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + "pencil" + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) if os.path.exists(pencil_icon): edit_btn.setIcon(QtGui.QIcon(pencil_icon)) edit_btn.setStyleSheet(circle_style) edit_btn.clicked.connect(partial(self.edit_button_clicked, btn)) edit_btn.show() - + # Create delete icon (top-right) delete_btn = QPushButton(btn.icon_container) delete_btn.setGeometry(btn.width() - 23, 3, 16, 16) - del_icon = os.path.join(os.path.dirname(sys.argv[0]), - 'icons', 'cross' + ('_dark' if colorMode=='dark' else '_light') + '.png') + del_icon = os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + "cross" + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) if os.path.exists(del_icon): delete_btn.setIcon(QtGui.QIcon(del_icon)) delete_btn.setStyleSheet(circle_style) delete_btn.clicked.connect(partial(self.delete_button_clicked, btn)) delete_btn.show() - + btn.icon_container.raise_() btn.icon_container.show() def toggle_edit_mode(self): """Toggle edit mode with improved button labels and state handling.""" self.edit_mode = not self.edit_mode - logging.debug(f'Edit mode toggled: {self.edit_mode}') + logging.debug(f"Edit mode toggled: {self.edit_mode}") if self.edit_mode: # Switch to edit mode: @@ -686,7 +735,7 @@ def toggle_edit_mode(self): padding: 0px; }} QPushButton:hover {{ - background-color: {'#333' if colorMode=='dark' else '#ebebeb'}; + background-color: {"#333" if colorMode == "dark" else "#ebebeb"}; }} """) # Hide close, show reset button & drag label @@ -707,7 +756,9 @@ def toggle_edit_mode(self): # Inform the user that the app will close to apply changes msg = QtWidgets.QMessageBox() msg.setWindowTitle("Quitting to apply changes...") - msg.setText("Writing Tools needs to relaunch to apply your changes & will now quit.\nPlease relaunch Writing Tools.exe to see your changes.") + msg.setText( + "Writing Tools needs to relaunch to apply your changes & will now quit.\nPlease relaunch Writing Tools.exe to see your changes." + ) msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.exec_() @@ -717,12 +768,11 @@ def toggle_edit_mode(self): QtCore.QTimer.singleShot(100, self.app.exit_app) return - # Update the edit button icon now that icon_name is defined icon_path = os.path.join( os.path.dirname(sys.argv[0]), - 'icons', - f"{icon_name}_{'dark' if colorMode=='dark' else 'light'}.png" + "icons", + f"{icon_name}_{'dark' if colorMode == 'dark' else 'light'}.png", ) if os.path.exists(icon_path): self.edit_button.setIcon(QtGui.QIcon(icon_path)) @@ -739,7 +789,7 @@ def toggle_edit_mode(self): if not self.edit_mode: btn.clicked.connect(partial(self.on_generic_instruction, btn.key)) - if hasattr(btn, 'icon_container') and btn.icon_container: + if hasattr(btn, "icon_container") and btn.icon_container: btn.icon_container.deleteLater() btn.icon_container = None else: @@ -750,20 +800,23 @@ def toggle_edit_mode(self): # Rebuild grid layout self.rebuild_grid_layout() - def on_reset_clicked(self): """ Reset `options.json` to the DEFAULT_OPTIONS_JSON, then show message & restart. """ confirm_box = QtWidgets.QMessageBox() confirm_box.setWindowTitle("Confirm Reset to Defaults & Quit?") - confirm_box.setText("To reset the buttons to their original configuration, Writing Tools would need to quit, so you'd need to relaunch Writing Tools.exe.\nAre you sure you want to continue?") - confirm_box.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + confirm_box.setText( + "To reset the buttons to their original configuration, Writing Tools would need to quit, so you'd need to relaunch Writing Tools.exe.\nAre you sure you want to continue?" + ) + confirm_box.setStandardButtons( + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No + ) confirm_box.setDefaultButton(QtWidgets.QMessageBox.No) - + if confirm_box.exec_() == QtWidgets.QMessageBox.Yes: try: - logging.debug('Resetting to default options.json') + logging.debug("Resetting to default options.json") default_data = json.loads(DEFAULT_OPTIONS_JSON) self.save_options(default_data) @@ -771,7 +824,7 @@ def on_reset_clicked(self): self.app.load_options() self.close() QtCore.QTimer.singleShot(100, self.app.exit_app) - + except Exception as e: logging.error(f"Error resetting options.json: {e}") error_msg = QtWidgets.QMessageBox() @@ -788,7 +841,7 @@ def add_new_button_clicked(self): "prefix": bd["prefix"], "instruction": bd["instruction"], "icon": bd["icon"], # uses 'icons/custom' - "open_in_window": bd["open_in_window"] + "open_in_window": bd["open_in_window"], } self.save_options(data) @@ -796,25 +849,24 @@ def add_new_button_clicked(self): self.rebuild_grid_layout() self.hide() - + QtWidgets.QMessageBox.information( - self, + self, "Quitting to apply button...", - "Writing Tools needs to relaunch to apply your fancy button & will now quit.\nPlease relaunch Writing Tools.exe to see your new button." + "Writing Tools needs to relaunch to apply your fancy button & will now quit.\nPlease relaunch Writing Tools.exe to see your new button.", ) self.app.load_options() self.close() QtCore.QTimer.singleShot(100, self.app.exit_app) - def edit_button_clicked(self, btn): """User clicked the small pencil icon over a button.""" key = btn.key data = self.load_options() bd = data[key] bd["name"] = key - + dialog = ButtonEditDialog(self, bd) if dialog.exec_(): new_data = dialog.get_button_data() @@ -825,7 +877,7 @@ def edit_button_clicked(self, btn): "prefix": new_data["prefix"], "instruction": new_data["instruction"], "icon": new_data["icon"], - "open_in_window": new_data["open_in_window"] + "open_in_window": new_data["open_in_window"], } self.save_options(data) @@ -836,9 +888,9 @@ def edit_button_clicked(self, btn): # Show message about relaunch requirement QtWidgets.QMessageBox.information( - self, + self, "Quitting to apply changes to this button...", - "Writing Tools needs to relaunch to apply your changes & will now quit.\nPlease relaunch Writing Tools.exe to see your changes." + "Writing Tools needs to relaunch to apply your changes & will now quit.\nPlease relaunch Writing Tools.exe to see your changes.", ) # Save and quit @@ -851,10 +903,12 @@ def delete_button_clicked(self, btn): key = btn.key confirm = QtWidgets.QMessageBox() confirm.setWindowTitle("Confirm Delete & Quit?") - confirm.setText(f"To delete the '{key}' button, Writing Tools would need to quit, so you'd need to relaunch Writing Tools.exe.\nAre you sure you want to continue?") + confirm.setText( + f"To delete the '{key}' button, Writing Tools would need to quit, so you'd need to relaunch Writing Tools.exe.\nAre you sure you want to continue?" + ) confirm.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) confirm.setDefaultButton(QtWidgets.QMessageBox.No) - + if confirm.exec_() == QtWidgets.QMessageBox.Yes: try: data = self.load_options() @@ -864,20 +918,22 @@ def delete_button_clicked(self, btn): # Clean up UI elements for btn_ in self.button_widgets[:]: if btn_.key == key: - if hasattr(btn_, 'icon_container') and btn_.icon_container: + if hasattr(btn_, "icon_container") and btn_.icon_container: btn_.icon_container.deleteLater() btn_.deleteLater() self.button_widgets.remove(btn_) - + self.app.load_options() self.close() QtCore.QTimer.singleShot(100, self.app.exit_app) - + except Exception as e: logging.error(f"Error deleting button: {e}") error_msg = QtWidgets.QMessageBox() error_msg.setWindowTitle("Error") - error_msg.setText(f"An error occurred while deleting the button: {str(e)}") + error_msg.setText( + f"An error occurred while deleting the button: {str(e)}" + ) error_msg.exec_() def update_json_from_grid(self): @@ -894,7 +950,7 @@ def update_json_from_grid(self): def on_custom_change(self): txt = self.custom_input.text().strip() if txt: - self.app.process_option('Custom', self.selected_text, txt) + self.app.process_option("Custom", self.selected_text, txt) self.close() def on_generic_instruction(self, instruction): @@ -904,14 +960,14 @@ def on_generic_instruction(self, instruction): def eventFilter(self, obj, event): # Hide on deactivate only if NOT in edit mode - if event.type()==QtCore.QEvent.WindowDeactivate: + if event.type() == QtCore.QEvent.WindowDeactivate: if not self.edit_mode: self.hide() return True return super().eventFilter(obj, event) def keyPressEvent(self, event): - if event.key()==QtCore.Qt.Key_Escape: + if event.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(event) diff --git a/Windows_and_Linux/ui/OnboardingWindow.py b/Windows_and_Linux/ui/OnboardingWindow.py index b3613c3..f092216 100644 --- a/Windows_and_Linux/ui/OnboardingWindow.py +++ b/Windows_and_Linux/ui/OnboardingWindow.py @@ -7,6 +7,7 @@ _ = lambda x: x + class OnboardingWindow(QtWidgets.QWidget): # Closing signal close_signal = QtCore.Signal() @@ -14,16 +15,16 @@ class OnboardingWindow(QtWidgets.QWidget): def __init__(self, app): super().__init__() self.app = app - self.shortcut = 'ctrl+space' - self.theme = 'gradient' + self.shortcut = "ctrl+space" + self.theme = "gradient" self.content_layout = None self.shortcut_input = None self.init_ui() self.self_close = False def init_ui(self): - logging.debug('Initializing onboarding UI') - self.setWindowTitle(_('Welcome to Writing Tools')) + logging.debug("Initializing onboarding UI") + self.setWindowTitle(_("Welcome to Writing Tools")) self.resize(600, 500) UIUtils.setup_window_and_layout(self) @@ -39,57 +40,73 @@ def init_ui(self): def show_welcome_screen(self): UIUtils.clear_layout(self.content_layout) - title_label = QtWidgets.QLabel(_("Welcome to Writing Tools")+"!") - title_label.setStyleSheet(f"font-size: 24px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") - self.content_layout.addWidget(title_label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter) + title_label = QtWidgets.QLabel(_("Welcome to Writing Tools") + "!") + title_label.setStyleSheet( + f"font-size: 24px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) + self.content_layout.addWidget( + title_label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) features_text = f""" • {_('Instantly optimize your writing with AI by selecting your text and invoking Writing Tools with "ctrl+space", anywhere.')} • {_('Get a summary you can chat with of articles, YouTube videos, or documents by select all text with "ctrl+a"')} - {_('(or select the YouTube transcript from its description), invoking Writing Tools, and choosing Summary.')} + {_("(or select the YouTube transcript from its description), invoking Writing Tools, and choosing Summary.")} - • {_('Chat with AI anytime by invoking Writing Tools without selecting any text.')} + • {_("Chat with AI anytime by invoking Writing Tools without selecting any text.")} - • {_('Supports an extensive range of AI models:')} - - {_('Gemini 2.0')} - - {_('ANY OpenAI Compatible API — including local LLMs!')} + • {_("Supports an extensive range of AI models:")} + - {_("Gemini 2.0")} + - {_("ANY OpenAI Compatible API — including local LLMs!")} """ features_label = QtWidgets.QLabel(features_text) - features_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + features_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) features_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft) self.content_layout.addWidget(features_label) - shortcut_label = QtWidgets.QLabel("Customize your shortcut key (default: \"ctrl+space\"):") - shortcut_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + shortcut_label = QtWidgets.QLabel( + 'Customize your shortcut key (default: "ctrl+space"):' + ) + shortcut_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) self.content_layout.addWidget(shortcut_label) self.shortcut_input = QtWidgets.QLineEdit(self.shortcut) self.shortcut_input.setStyleSheet(f""" font-size: 16px; padding: 5px; - background-color: {'#444' if colorMode == 'dark' else 'white'}; - color: {'#ffffff' if colorMode == 'dark' else '#000000'}; - border: 1px solid {'#666' if colorMode == 'dark' else '#ccc'}; + background-color: {"#444" if colorMode == "dark" else "white"}; + color: {"#ffffff" if colorMode == "dark" else "#000000"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; """) self.content_layout.addWidget(self.shortcut_input) theme_label = QtWidgets.QLabel(_("Choose your theme:")) - theme_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + theme_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) self.content_layout.addWidget(theme_label) theme_layout = QHBoxLayout() gradient_radio = QRadioButton(_("Gradient")) plain_radio = QRadioButton(_("Plain")) - gradient_radio.setStyleSheet(f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};") - plain_radio.setStyleSheet(f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};") - gradient_radio.setChecked(self.theme == 'gradient') - plain_radio.setChecked(self.theme == 'plain') + gradient_radio.setStyleSheet( + f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) + plain_radio.setStyleSheet( + f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) + gradient_radio.setChecked(self.theme == "gradient") + plain_radio.setChecked(self.theme == "plain") theme_layout.addWidget(gradient_radio) theme_layout.addWidget(plain_radio) self.content_layout.addLayout(theme_layout) - next_button = QtWidgets.QPushButton(_('Next')) + next_button = QtWidgets.QPushButton(_("Next")) next_button.setStyleSheet(""" QPushButton { background-color: #4CAF50; @@ -103,17 +120,16 @@ def show_welcome_screen(self): background-color: #45a049; } """) - next_button.clicked.connect(lambda: self.on_next_clicked(gradient_radio.isChecked())) + next_button.clicked.connect( + lambda: self.on_next_clicked(gradient_radio.isChecked()) + ) self.content_layout.addWidget(next_button) def on_next_clicked(self, is_gradient): self.shortcut = self.shortcut_input.text() - self.theme = 'gradient' if is_gradient else 'plain' - logging.debug(f'User selected shortcut: {self.shortcut}, theme: {self.theme}') - self.app.config = { - 'shortcut': self.shortcut, - 'theme': self.theme - } + self.theme = "gradient" if is_gradient else "plain" + logging.debug(f"User selected shortcut: {self.shortcut}, theme: {self.theme}") + self.app.config = {"shortcut": self.shortcut, "theme": self.theme} self.show_api_key_input() def show_api_key_input(self): diff --git a/Windows_and_Linux/ui/ResponseWindow.py b/Windows_and_Linux/ui/ResponseWindow.py index ad301ce..b003fe4 100644 --- a/Windows_and_Linux/ui/ResponseWindow.py +++ b/Windows_and_Linux/ui/ResponseWindow.py @@ -11,9 +11,10 @@ _ = lambda x: x + class MarkdownTextBrowser(QtWidgets.QTextBrowser): """Enhanced text browser for displaying Markdown content with improved sizing""" - + def __init__(self, parent=None, is_user_message=False): super().__init__(parent) self.setReadOnly(True) @@ -21,28 +22,27 @@ def __init__(self, parent=None, is_user_message=False): self.zoom_factor = 1.2 self.base_font_size = 14 self.is_user_message = is_user_message - + # Critical: Remove scrollbars to prevent extra space self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) - + # Set size policies to prevent unwanted expansion self.setSizePolicy( - QtWidgets.QSizePolicy.Policy.Expanding, - QtWidgets.QSizePolicy.Policy.Minimum + QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum ) - + self._apply_zoom() - + def _apply_zoom(self): new_size = int(self.base_font_size * self.zoom_factor) - + # Updated stylesheet with table styling self.setStyleSheet(f""" QTextBrowser {{ - background-color: {('transparent' if self.is_user_message else '#333' if colorMode == 'dark' else 'white')}; - color: {'#ffffff' if colorMode == 'dark' else '#000000'}; - border: {('none' if self.is_user_message else '1px solid ' + ('#555' if colorMode == 'dark' else '#ccc'))}; + background-color: {("transparent" if self.is_user_message else "#333" if colorMode == "dark" else "white")}; + color: {"#ffffff" if colorMode == "dark" else "#000000"}; + border: {("none" if self.is_user_message else "1px solid " + ("#555" if colorMode == "dark" else "#ccc"))}; border-radius: 8px; padding: 8px; margin: 0px; @@ -57,48 +57,48 @@ def _apply_zoom(self): width: 100%; margin: 10px 0; }} - + th, td {{ - border: 1px solid {'#555' if colorMode == 'dark' else '#ccc'}; + border: 1px solid {"#555" if colorMode == "dark" else "#ccc"}; padding: 8px; text-align: left; }} - + th {{ - background-color: {'#444' if colorMode == 'dark' else '#f5f5f5'}; + background-color: {"#444" if colorMode == "dark" else "#f5f5f5"}; font-weight: bold; }} - + tr:nth-child(even) {{ - background-color: {'#3a3a3a' if colorMode == 'dark' else '#f9f9f9'}; + background-color: {"#3a3a3a" if colorMode == "dark" else "#f9f9f9"}; }} - + tr:hover {{ - background-color: {'#484848' if colorMode == 'dark' else '#f0f0f0'}; + background-color: {"#484848" if colorMode == "dark" else "#f0f0f0"}; }} """) - + def _update_size(self): # Calculate correct document width available_width = self.viewport().width() - 16 # Account for padding self.document().setTextWidth(available_width) - + # Get precise content height doc_size = self.document().size() content_height = doc_size.height() - + # Add minimal padding for content new_height = int(content_height + 16) # Reduced total padding - + if self.minimumHeight() != new_height: self.setMinimumHeight(new_height) self.setMaximumHeight(new_height) # Force fixed height - + # Update scroll area if needed scroll_area = self.get_scroll_area() if scroll_area: scroll_area.update_content_height() - + def wheelEvent(self, event): if event.modifiers() == Qt.KeyboardModifier.ControlModifier: delta = event.angleDelta().y() @@ -106,39 +106,39 @@ def wheelEvent(self, event): parent = self.parent() while parent and not isinstance(parent, ResponseWindow): parent = parent.parent() - + if parent: if delta > 0: - parent.zoom_all_messages('in') + parent.zoom_all_messages("in") else: - parent.zoom_all_messages('out') + parent.zoom_all_messages("out") event.accept() else: # Pass wheel events to parent for scrolling if self.parent(): self.parent().wheelEvent(event) - + def zoom_in(self): old_factor = self.zoom_factor self.zoom_factor = min(3.0, self.zoom_factor * 1.1) if old_factor != self.zoom_factor: self._apply_zoom() self._update_size() - + def zoom_out(self): old_factor = self.zoom_factor self.zoom_factor = max(0.5, self.zoom_factor / 1.1) if old_factor != self.zoom_factor: self._apply_zoom() self._update_size() - + def reset_zoom(self): old_factor = self.zoom_factor self.zoom_factor = 1.2 # Reset to default zoom if old_factor != self.zoom_factor: self._apply_zoom() self._update_size() - + def get_scroll_area(self): """Find the parent ChatContentScrollArea""" parent = self.parent() @@ -147,7 +147,7 @@ def get_scroll_area(self): return parent parent = parent.parent() return None - + def resizeEvent(self, event): super().resizeEvent(event) self._update_size() @@ -155,32 +155,32 @@ def resizeEvent(self, event): class ChatContentScrollArea(QScrollArea): """Improved scrollable container for chat messages with dynamic sizing and proper spacing""" - + def __init__(self, parent=None): super().__init__(parent) self.content_widget = None self.layout = None self.setup_ui() - + def setup_ui(self): self.setWidgetResizable(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) - + # Main container widget with explicit size policy self.content_widget = QtWidgets.QWidget() self.content_widget.setSizePolicy( QtWidgets.QSizePolicy.Policy.Preferred, - QtWidgets.QSizePolicy.Policy.MinimumExpanding + QtWidgets.QSizePolicy.Policy.MinimumExpanding, ) self.setWidget(self.content_widget) - + # Main layout with improved spacing self.layout = QtWidgets.QVBoxLayout(self.content_widget) self.layout.setSpacing(8) # Reduced spacing between messages self.layout.setContentsMargins(15, 15, 15, 15) # Adjusted margins self.layout.addStretch() - + # Enhanced scroll area styling self.setStyleSheet(""" QScrollArea { @@ -209,41 +209,40 @@ def setup_ui(self): def add_message(self, text, is_user=False): # Remove bottom stretch self.layout.takeAt(self.layout.count() - 1) - + # Create message container with improved width msg_container = QtWidgets.QWidget() msg_container.setSizePolicy( - QtWidgets.QSizePolicy.Policy.Expanding, - QtWidgets.QSizePolicy.Policy.Minimum + QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum ) - + # Message layout with minimal margins msg_layout = QtWidgets.QVBoxLayout(msg_container) msg_layout.setContentsMargins(0, 0, 0, 0) msg_layout.setSpacing(0) - + # Create text display with updated width text_display = MarkdownTextBrowser(is_user_message=is_user) - + # Enable tables extension in markdown2 - html = markdown2.markdown(text, extras=['tables', 'strike']) + html = markdown2.markdown(text, extras=["tables", "strike"]) text_display.setHtml(html) - + # Calculate proper text display size using full width text_display.document().setTextWidth(self.width() - 20) doc_size = text_display.document().size() text_display.setMinimumHeight(int(doc_size.height() + 16)) - + msg_layout.addWidget(text_display) - + self.layout.addWidget(msg_container) self.layout.addStretch() - - if hasattr(self.parent(), 'current_text_display'): + + if hasattr(self.parent(), "current_text_display"): self.parent().current_text_display = text_display - + QtCore.QTimer.singleShot(50, self.post_message_updates) - + return text_display def post_message_updates(self): @@ -255,21 +254,25 @@ def post_message_updates(self): def update_content_height(self): """Recalculate total content height with improved spacing calculation""" total_height = 0 - + # Calculate height of all messages for i in range(self.layout.count() - 1): # Skip stretch item item = self.layout.itemAt(i) if item and item.widget(): widget_height = item.widget().sizeHint().height() total_height += widget_height - + # Add spacing between messages and margins - total_height += (self.layout.spacing() * (self.layout.count() - 2)) # Message spacing - total_height += self.layout.contentsMargins().top() + self.layout.contentsMargins().bottom() - + total_height += self.layout.spacing() * ( + self.layout.count() - 2 + ) # Message spacing + total_height += ( + self.layout.contentsMargins().top() + self.layout.contentsMargins().bottom() + ) + # Set minimum height with some padding self.content_widget.setMinimumHeight(total_height + 10) - + # Update window height if needed if isinstance(self.parent(), ResponseWindow): self.parent()._adjust_window_height() @@ -282,7 +285,7 @@ def scroll_to_bottom(self): def resizeEvent(self, event): """Handle resize events with improved width calculations""" super().resizeEvent(event) - + # Update width for all message displays available_width = self.width() - 40 # Account for margins for i in range(self.layout.count() - 1): # Skip stretch item @@ -294,12 +297,14 @@ def resizeEvent(self, event): # Recalculate text width and height text_display.document().setTextWidth(available_width) doc_size = text_display.document().size() - text_display.setMinimumHeight(int(doc_size.height() + 20)) # Reduced padding + text_display.setMinimumHeight( + int(doc_size.height() + 20) + ) # Reduced padding class ResponseWindow(QtWidgets.QWidget): """Enhanced response window with improved sizing and zoom handling""" - + def __init__(self, app, title=_("Response"), parent=None): super().__init__(parent) self.app = app @@ -321,23 +326,25 @@ def __init__(self, app, title=_("Response"), parent=None): self.thinking_timer.setInterval(300) self.init_ui() - logging.debug('Connecting response signals') + logging.debug("Connecting response signals") self.app.followup_response_signal.connect(self.handle_followup_response) - logging.debug('Response signals connected') + logging.debug("Response signals connected") # Set initial size for "Thinking..." state initial_width = 500 initial_height = 250 self.resize(initial_width, initial_height) - + def init_ui(self): # Window setup with enhanced flags - self.setWindowFlags(QtCore.Qt.WindowType.Window | - QtCore.Qt.WindowType.WindowCloseButtonHint | - QtCore.Qt.WindowType.WindowMinimizeButtonHint | - QtCore.Qt.WindowType.WindowMaximizeButtonHint) + self.setWindowFlags( + QtCore.Qt.WindowType.Window + | QtCore.Qt.WindowType.WindowCloseButtonHint + | QtCore.Qt.WindowType.WindowMinimizeButtonHint + | QtCore.Qt.WindowType.WindowMaximizeButtonHint + ) self.setMinimumSize(600, 400) - + # Main layout setup UIUtils.setup_window_and_layout(self) content_layout = QtWidgets.QVBoxLayout(self.background) @@ -346,50 +353,64 @@ def init_ui(self): # Top bar with zoom controls top_bar = QtWidgets.QHBoxLayout() - + title_label = QtWidgets.QLabel(self.option) - title_label.setStyleSheet(f"font-size: 20px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + title_label.setStyleSheet( + f"font-size: 20px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) top_bar.addWidget(title_label) - + top_bar.addStretch() # Zoom label with matched size zoom_label = QtWidgets.QLabel("Zoom:") zoom_label.setStyleSheet(f""" - color: {'#aaaaaa' if colorMode == 'dark' else '#666666'}; + color: {"#aaaaaa" if colorMode == "dark" else "#666666"}; font-size: 14px; margin-right: 5px; """) top_bar.addWidget(zoom_label) - + # Enhanced zoom controls with swapped order zoom_controls = [ - ('plus', 'Zoom In', lambda: self.zoom_all_messages('in')), - ('minus', 'Zoom Out', lambda: self.zoom_all_messages('out')), - ('reset', 'Reset Zoom', lambda: self.zoom_all_messages('reset')) + ("plus", "Zoom In", lambda: self.zoom_all_messages("in")), + ("minus", "Zoom Out", lambda: self.zoom_all_messages("out")), + ("reset", "Reset Zoom", lambda: self.zoom_all_messages("reset")), ] - + for icon, tooltip, action in zoom_controls: btn = QtWidgets.QPushButton() - btn.setIcon(QtGui.QIcon(os.path.join(os.path.dirname(sys.argv[0]), 'icons', icon + ('_dark' if colorMode == 'dark' else '_light') + '.png'))) + btn.setIcon( + QtGui.QIcon( + os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + icon + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) + ) + ) btn.setStyleSheet(self.get_button_style()) btn.setToolTip(tooltip) btn.clicked.connect(action) btn.setFixedSize(30, 30) top_bar.addWidget(btn) - + content_layout.addLayout(top_bar) # Copy controls with matching text size copy_bar = QtWidgets.QHBoxLayout() copy_hint = QtWidgets.QLabel(_("Select to copy with formatting")) - copy_hint.setStyleSheet(f"color: {'#aaaaaa' if colorMode == 'dark' else '#666666'}; font-size: 14px;") + copy_hint.setStyleSheet( + f"color: {'#aaaaaa' if colorMode == 'dark' else '#666666'}; font-size: 14px;" + ) copy_bar.addWidget(copy_hint) copy_bar.addStretch() - + copy_md_btn = QtWidgets.QPushButton(_("Copy as Markdown")) copy_md_btn.setStyleSheet(self.get_button_style()) - copy_md_btn.clicked.connect(self.copy_first_response) # Updated to only copy first response + copy_md_btn.clicked.connect( + self.copy_first_response + ) # Updated to only copy first response copy_bar.addWidget(copy_md_btn) content_layout.addLayout(copy_bar) @@ -397,72 +418,82 @@ def init_ui(self): loading_container = QtWidgets.QWidget() loading_layout = QtWidgets.QHBoxLayout(loading_container) loading_layout.setContentsMargins(0, 0, 0, 0) - + self.loading_label = QtWidgets.QLabel(_("Thinking")) self.loading_label.setStyleSheet(f""" QLabel {{ - color: {'#ffffff' if colorMode == 'dark' else '#333333'}; + color: {"#ffffff" if colorMode == "dark" else "#333333"}; font-size: 18px; padding: 20px; }} """) self.loading_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft) - + loading_inner_container = QtWidgets.QWidget() loading_inner_container.setFixedWidth(180) loading_inner_layout = QtWidgets.QHBoxLayout(loading_inner_container) loading_inner_layout.setContentsMargins(0, 0, 0, 0) loading_inner_layout.addWidget(self.loading_label) - + loading_layout.addStretch() loading_layout.addWidget(loading_inner_container) loading_layout.addStretch() - + content_layout.addWidget(loading_container) self.loading_container = loading_container - + # Start thinking animation self.start_thinking_animation(initial=True) - + # Enhanced chat area with full width self.chat_area = ChatContentScrollArea() content_layout.addWidget(self.chat_area) - + # Input area with enhanced styling bottom_bar = QtWidgets.QHBoxLayout() - + self.input_field = QtWidgets.QLineEdit() - self.input_field.setPlaceholderText(_("Ask a follow-up question")+'...') + self.input_field.setPlaceholderText(_("Ask a follow-up question") + "...") self.input_field.setStyleSheet(f""" QLineEdit {{ padding: 8px; - border: 1px solid {'#777' if colorMode == 'dark' else '#ccc'}; + border: 1px solid {"#777" if colorMode == "dark" else "#ccc"}; border-radius: 8px; - background-color: {'#333' if colorMode == 'dark' else 'white'}; - color: {'#ffffff' if colorMode == 'dark' else '#000000'}; + background-color: {"#333" if colorMode == "dark" else "white"}; + color: {"#ffffff" if colorMode == "dark" else "#000000"}; font-size: 14px; }} """) self.input_field.returnPressed.connect(self.send_message) bottom_bar.addWidget(self.input_field) - + send_button = QtWidgets.QPushButton() - send_button.setIcon(QtGui.QIcon(os.path.join(os.path.dirname(sys.argv[0]), 'icons', 'send' + ('_dark' if colorMode == 'dark' else '_light') + '.png'))) + send_button.setIcon( + QtGui.QIcon( + os.path.join( + os.path.dirname(sys.argv[0]), + "icons", + "send" + ("_dark" if colorMode == "dark" else "_light") + ".png", + ) + ) + ) send_button.setStyleSheet(f""" QPushButton {{ - background-color: {'#2e7d32' if colorMode == 'dark' else '#4CAF50'}; + background-color: {"#2e7d32" if colorMode == "dark" else "#4CAF50"}; border: none; border-radius: 8px; padding: 5px; }} QPushButton:hover {{ - background-color: {'#1b5e20' if colorMode == 'dark' else '#45a049'}; + background-color: {"#1b5e20" if colorMode == "dark" else "#45a049"}; }} """) - send_button.setFixedSize(self.input_field.sizeHint().height(), self.input_field.sizeHint().height()) + send_button.setFixedSize( + self.input_field.sizeHint().height(), self.input_field.sizeHint().height() + ) send_button.clicked.connect(self.send_message) bottom_bar.addWidget(send_button) - + content_layout.addLayout(bottom_bar) # Method to get first response text @@ -472,12 +503,12 @@ def get_first_response_text(self): # Check chat history exists if not self.chat_history: return None - + # Find first assistant message for msg in self.chat_history: if msg["role"] == "assistant": return msg["content"] - + return None except Exception as e: logging.error(f"Error getting first response: {e}") @@ -492,32 +523,34 @@ def copy_first_response(self): def get_button_style(self): return f""" QPushButton {{ - background-color: {'#444' if colorMode == 'dark' else '#f0f0f0'}; - color: {'#ffffff' if colorMode == 'dark' else '#000000'}; - border: 1px solid {'#666' if colorMode == 'dark' else '#ccc'}; + background-color: {"#444" if colorMode == "dark" else "#f0f0f0"}; + color: {"#ffffff" if colorMode == "dark" else "#000000"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; border-radius: 5px; padding: 8px; font-size: 14px; }} QPushButton:hover {{ - background-color: {'#555' if colorMode == 'dark' else '#e0e0e0'}; + background-color: {"#555" if colorMode == "dark" else "#e0e0e0"}; }} """ def update_thinking_dots(self): """Update the thinking animation dots with proper cycling""" - self.thinking_dots_state = (self.thinking_dots_state + 1) % len(self.thinking_dots) + self.thinking_dots_state = (self.thinking_dots_state + 1) % len( + self.thinking_dots + ) dots = self.thinking_dots[self.thinking_dots_state] - + if self.loading_label.isVisible(): - self.loading_label.setText(_("Thinking")+f"{dots}") + self.loading_label.setText(_("Thinking") + f"{dots}") else: - self.input_field.setPlaceholderText(_("Thinking")+f"{dots}") - + self.input_field.setPlaceholderText(_("Thinking") + f"{dots}") + def start_thinking_animation(self, initial=False): """Start the thinking animation for either initial load or follow-up questions""" self.thinking_dots_state = 0 - + if initial: self.loading_label.setText(_("Thinking")) self.loading_label.setVisible(True) @@ -525,7 +558,7 @@ def start_thinking_animation(self, initial=False): else: self.input_field.setPlaceholderText(_("Thinking")) self.loading_container.setVisible(False) - + self.thinking_timer.start() def stop_thinking_animation(self): @@ -535,81 +568,80 @@ def stop_thinking_animation(self): self.loading_label.hide() self.input_field.setPlaceholderText(_("Ask a follow-up question")) self.input_field.setEnabled(True) - + # Force layout update if self.layout(): self.layout().invalidate() self.layout().activate() - def zoom_all_messages(self, action='in'): + def zoom_all_messages(self, action="in"): """Apply zoom action to all messages in the chat""" for i in range(self.chat_area.layout.count() - 1): # Skip stretch item item = self.chat_area.layout.itemAt(i) if item and item.widget(): text_display = item.widget().layout().itemAt(0).widget() if isinstance(text_display, MarkdownTextBrowser): - if action == 'in': + if action == "in": text_display.zoom_in() - elif action == 'out': + elif action == "out": text_display.zoom_out() else: # reset text_display.reset_zoom() - + # Update layout after zooming self.chat_area.update_content_height() - + def _adjust_window_height(self): """Calculate and set the ideal window height""" # Skip adjustment if window already has a size - if hasattr(self, '_size_initialized'): + if hasattr(self, "_size_initialized"): return - + try: # Get content widget height content_height = self.chat_area.content_widget.sizeHint().height() - + # Calculate other UI elements height ui_elements_height = ( - self.layout().contentsMargins().top() + - self.layout().contentsMargins().bottom() + - self.input_field.height() + - self.layout().spacing() * 5 + - 200 # Increased from 185 for taller default height + self.layout().contentsMargins().top() + + self.layout().contentsMargins().bottom() + + self.input_field.height() + + self.layout().spacing() * 5 + + 200 # Increased from 185 for taller default height ) - + # Get screen constraints screen = QtWidgets.QApplication.screenAt(self.pos()) if not screen: screen = QtWidgets.QApplication.primaryScreen() - + # Calculate maximum available height (85% of screen) max_height = int(screen.geometry().height() * 0.85) - + # Calculate desired height to show more content initially desired_content_height = int(content_height * 0.85) # Show 85% of content desired_total_height = min( - desired_content_height + ui_elements_height, - max_height + desired_content_height + ui_elements_height, max_height ) - + # Set reasonable minimum height - increased by 10% final_height = max(600, desired_total_height) # Increased from 540 - + # Set width to 600px final_width = 600 - + # Update both width and height self.resize(final_width, final_height) - + # Center on screen frame_geometry = self.frameGeometry() screen_center = screen.geometry().center() frame_geometry.moveCenter(screen_center) self.move(frame_geometry.topLeft()) - + # Mark size as initialized self._size_initialized = True - + except Exception as e: logging.error(f"Error adjusting window height: {e}") self.resize(600, 600) # Updated fallback size @@ -620,66 +652,68 @@ def set_text(self, text): """Set initial response text with enhanced handling""" if not text.strip(): return - + # Always ensure chat history is initialized properly self.chat_history = [ {"role": "user", "content": f"{self.option}: {self.selected_text}"}, - {"role": "assistant", "content": text} # Add initial response immediately + {"role": "assistant", "content": text}, # Add initial response immediately ] - + self.stop_thinking_animation() text_display = self.chat_area.add_message(text) - + # Update zoom state - if hasattr(self.app.config, 'response_window_zoom'): - text_display.zoom_factor = self.app.config['response_window_zoom'] + if hasattr(self.app.config, "response_window_zoom"): + text_display.zoom_factor = self.app.config["response_window_zoom"] text_display._apply_zoom() - + QtCore.QTimer.singleShot(100, self._adjust_window_height) - + @Slot(str) def handle_followup_response(self, response_text): """Handle the follow-up response from the AI with improved layout handling""" if response_text: self.loading_label.setVisible(False) text_display = self.chat_area.add_message(response_text) - + # Maintain consistent zoom level - if hasattr(self, 'current_text_display'): + if hasattr(self, "current_text_display"): text_display.zoom_factor = self.current_text_display.zoom_factor text_display._apply_zoom() - - if len(self.chat_history) > 0 and self.chat_history[-1]["role"] != "assistant": - self.chat_history.append({ - "role": "assistant", - "content": response_text - }) - + + if ( + len(self.chat_history) > 0 + and self.chat_history[-1]["role"] != "assistant" + ): + self.chat_history.append( + {"role": "assistant", "content": response_text} + ) + self.stop_thinking_animation() self.input_field.setEnabled(True) - + # Update window height QtCore.QTimer.singleShot(100, self._adjust_window_height) - + def send_message(self): """Send a new message/question""" message = self.input_field.text().strip() if not message: return - + self.input_field.setEnabled(False) self.input_field.clear() - + # Add user message and maintain zoom level text_display = self.chat_area.add_message(message, is_user=True) - if hasattr(self, 'current_text_display'): + if hasattr(self, "current_text_display"): text_display.zoom_factor = self.current_text_display.zoom_factor text_display._apply_zoom() - + self.chat_history.append({"role": "user", "content": message}) self.start_thinking_animation() self.app.process_followup_question(self, message) - + def copy_as_markdown(self): """Copy conversation as Markdown""" markdown = "" @@ -688,20 +722,21 @@ def copy_as_markdown(self): markdown += f"**User**: {msg['content']}\n\n" else: markdown += f"**Assistant**: {msg['content']}\n\n" - + QtWidgets.QApplication.clipboard().setText(markdown) - + def closeEvent(self, event): """Handle window close event""" # Save zoom factor to main config - if hasattr(self, 'current_text_display'): - self.app.config['response_window_zoom'] = self.current_text_display.zoom_factor + if hasattr(self, "current_text_display"): + self.app.config["response_window_zoom"] = ( + self.current_text_display.zoom_factor + ) self.app.save_config(self.app.config) self.chat_history = [] - - if hasattr(self.app, 'current_response_window'): - delattr(self.app, 'current_response_window') - + + if hasattr(self.app, "current_response_window"): + delattr(self.app, "current_response_window") super().closeEvent(event) diff --git a/Windows_and_Linux/ui/SettingsWindow.py b/Windows_and_Linux/ui/SettingsWindow.py index 733c0da..5c395ce 100644 --- a/Windows_and_Linux/ui/SettingsWindow.py +++ b/Windows_and_Linux/ui/SettingsWindow.py @@ -11,11 +11,13 @@ _ = lambda x: x + class SettingsWindow(QtWidgets.QWidget): """ The settings window for the application. Now with scrolling support for better usability on smaller screens. """ + close_signal = QtCore.Signal() def __init__(self, app, providers_only=False): @@ -32,7 +34,6 @@ def __init__(self, app, providers_only=False): self.init_ui() self.retranslate_ui() - def retranslate_ui(self): self.setWindowTitle(_("Settings")) @@ -53,7 +54,9 @@ def init_provider_ui(self, provider: AIProvider, layout): provider_header_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) if provider.logo: - logo_path = os.path.join(os.path.dirname(sys.argv[0]), 'icons', f"provider_{provider.logo}.png") + logo_path = os.path.join( + os.path.dirname(sys.argv[0]), "icons", f"provider_{provider.logo}.png" + ) if os.path.exists(logo_path): targetPixmap = UIUtils.resize_and_round_image(QImage(logo_path), 30, 15) logo_label = QtWidgets.QLabel() @@ -62,7 +65,9 @@ def init_provider_ui(self, provider: AIProvider, layout): provider_header_layout.addWidget(logo_label) provider_name_label = QtWidgets.QLabel(provider.provider_name) - provider_name_label.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + provider_name_label.setStyleSheet( + f"font-size: 18px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) provider_name_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter) provider_header_layout.addWidget(provider_name_label) @@ -70,19 +75,21 @@ def init_provider_ui(self, provider: AIProvider, layout): if provider.description: description_label = QtWidgets.QLabel(provider.description) - description_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'}; text-align: center;") + description_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'}; text-align: center;" + ) description_label.setWordWrap(True) self.current_provider_layout.addWidget(description_label) - if hasattr(provider, 'ollama_button_text'): + if hasattr(provider, "ollama_button_text"): # Create container for buttons button_layout = QtWidgets.QHBoxLayout() - + # Add Ollama setup button ollama_button = QtWidgets.QPushButton(provider.ollama_button_text) ollama_button.setStyleSheet(f""" QPushButton {{ - background-color: {'#4CAF50' if colorMode == 'dark' else '#008CBA'}; + background-color: {"#4CAF50" if colorMode == "dark" else "#008CBA"}; color: white; padding: 10px; font-size: 16px; @@ -90,17 +97,17 @@ def init_provider_ui(self, provider: AIProvider, layout): border-radius: 5px; }} QPushButton:hover {{ - background-color: {'#45a049' if colorMode == 'dark' else '#007095'}; + background-color: {"#45a049" if colorMode == "dark" else "#007095"}; }} """) ollama_button.clicked.connect(provider.ollama_button_action) button_layout.addWidget(ollama_button) - + # Add original button main_button = QtWidgets.QPushButton(provider.button_text) main_button.setStyleSheet(f""" QPushButton {{ - background-color: {'#4CAF50' if colorMode == 'dark' else '#008CBA'}; + background-color: {"#4CAF50" if colorMode == "dark" else "#008CBA"}; color: white; padding: 10px; font-size: 16px; @@ -108,12 +115,12 @@ def init_provider_ui(self, provider: AIProvider, layout): border-radius: 5px; }} QPushButton:hover {{ - background-color: {'#45a049' if colorMode == 'dark' else '#007095'}; + background-color: {"#45a049" if colorMode == "dark" else "#007095"}; }} """) main_button.clicked.connect(provider.button_action) button_layout.addWidget(main_button) - + self.current_provider_layout.addLayout(button_layout) else: # Original single button logic @@ -121,7 +128,7 @@ def init_provider_ui(self, provider: AIProvider, layout): button = QtWidgets.QPushButton(provider.button_text) button.setStyleSheet(f""" QPushButton {{ - background-color: {'#4CAF50' if colorMode == 'dark' else '#008CBA'}; + background-color: {"#4CAF50" if colorMode == "dark" else "#008CBA"}; color: white; padding: 10px; font-size: 16px; @@ -129,11 +136,13 @@ def init_provider_ui(self, provider: AIProvider, layout): border-radius: 5px; }} QPushButton:hover {{ - background-color: {'#45a049' if colorMode == 'dark' else '#007095'}; + background-color: {"#45a049" if colorMode == "dark" else "#007095"}; }} """) button.clicked.connect(provider.button_action) - self.current_provider_layout.addWidget(button, alignment=QtCore.Qt.AlignmentFlag.AlignCenter) + self.current_provider_layout.addWidget( + button, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) # Initialize config if needed if "providers" not in self.app.config: @@ -143,7 +152,11 @@ def init_provider_ui(self, provider: AIProvider, layout): # Add provider settings for setting in provider.settings: - setting.set_value(self.app.config["providers"][provider.provider_name].get(setting.name, setting.default_value)) + setting.set_value( + self.app.config["providers"][provider.provider_name].get( + setting.name, setting.default_value + ) + ) setting.render_to_layout(self.current_provider_layout) layout.addLayout(self.current_provider_layout) @@ -153,7 +166,7 @@ def init_ui(self): Initialize the user interface for the settings window. Now includes a scroll area for better handling of content on smaller screens. """ - self.setWindowTitle(_('Settings')) + self.setWindowTitle(_("Settings")) # Set the exact width we want (592px) as both minimum and default self.setMinimumWidth(592) self.setFixedWidth(592) # This makes the width non-resizable @@ -162,19 +175,23 @@ def init_ui(self): UIUtils.setup_window_and_layout(self) main_layout = QtWidgets.QVBoxLayout(self.background) main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(10) # Add spacing between scroll area and bottom elements + main_layout.setSpacing( + 10 + ) # Add spacing between scroll area and bottom elements # Earlier scroll_area and scroll_content creation moved up # Create scroll area scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) - scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + scroll_area.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) # Create scroll content widget scroll_content = QtWidgets.QWidget() scroll_content.setStyleSheet("background: transparent;") - + # Style the scroll area for transparency scroll_area.setStyleSheet(""" QScrollArea { @@ -208,68 +225,92 @@ def init_ui(self): if not self.providers_only: title_label = QtWidgets.QLabel(_("Settings")) - title_label.setStyleSheet(f"font-size: 24px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") - content_layout.addWidget(title_label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter) + title_label.setStyleSheet( + f"font-size: 24px; font-weight: bold; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) + content_layout.addWidget( + title_label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter + ) # Add autostart checkbox for Windows compiled version if AutostartManager.get_startup_path(): self.autostart_checkbox = QtWidgets.QCheckBox(_("Start on Boot")) - self.autostart_checkbox.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + self.autostart_checkbox.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) self.autostart_checkbox.setChecked(AutostartManager.check_autostart()) self.autostart_checkbox.stateChanged.connect(self.toggle_autostart) content_layout.addWidget(self.autostart_checkbox) # Add shortcut key input shortcut_label = QtWidgets.QLabel(_("Shortcut Key:")) - shortcut_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + shortcut_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) content_layout.addWidget(shortcut_label) - self.shortcut_input = QtWidgets.QLineEdit(self.app.config.get('shortcut', 'ctrl+space')) + self.shortcut_input = QtWidgets.QLineEdit( + self.app.config.get("shortcut", "ctrl+space") + ) self.shortcut_input.setStyleSheet(f""" font-size: 16px; padding: 5px; - background-color: {'#444' if colorMode == 'dark' else 'white'}; - color: {'#ffffff' if colorMode == 'dark' else '#000000'}; - border: 1px solid {'#666' if colorMode == 'dark' else '#ccc'}; + background-color: {"#444" if colorMode == "dark" else "white"}; + color: {"#ffffff" if colorMode == "dark" else "#000000"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; """) content_layout.addWidget(self.shortcut_input) # Add theme selection theme_label = QtWidgets.QLabel(_("Background Theme:")) - theme_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + theme_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) content_layout.addWidget(theme_label) theme_layout = QHBoxLayout() self.gradient_radio = QRadioButton(_("Blurry Gradient")) self.plain_radio = QRadioButton(_("Plain")) - self.gradient_radio.setStyleSheet(f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};") - self.plain_radio.setStyleSheet(f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};") - current_theme = self.app.config.get('theme', 'gradient') - self.gradient_radio.setChecked(current_theme == 'gradient') - self.plain_radio.setChecked(current_theme == 'plain') + self.gradient_radio.setStyleSheet( + f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) + self.plain_radio.setStyleSheet( + f"color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) + current_theme = self.app.config.get("theme", "gradient") + self.gradient_radio.setChecked(current_theme == "gradient") + self.plain_radio.setChecked(current_theme == "plain") theme_layout.addWidget(self.gradient_radio) theme_layout.addWidget(self.plain_radio) content_layout.addLayout(theme_layout) # Add provider selection provider_label = QtWidgets.QLabel(_("Choose AI Provider:")) - provider_label.setStyleSheet(f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};") + provider_label.setStyleSheet( + f"font-size: 16px; color: {'#ffffff' if colorMode == 'dark' else '#333333'};" + ) content_layout.addWidget(provider_label) self.provider_dropdown = QtWidgets.QComboBox() self.provider_dropdown.setStyleSheet(f""" font-size: 16px; padding: 5px; - background-color: {'#444' if colorMode == 'dark' else 'white'}; - color: {'#ffffff' if colorMode == 'dark' else '#000000'}; - border: 1px solid {'#666' if colorMode == 'dark' else '#ccc'}; + background-color: {"#444" if colorMode == "dark" else "white"}; + color: {"#ffffff" if colorMode == "dark" else "#000000"}; + border: 1px solid {"#666" if colorMode == "dark" else "#ccc"}; """) - self.provider_dropdown.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.NoInsert) + self.provider_dropdown.setInsertPolicy( + QtWidgets.QComboBox.InsertPolicy.NoInsert + ) - current_provider = self.app.config.get('provider', self.app.providers[0].provider_name) + current_provider = self.app.config.get( + "provider", self.app.providers[0].provider_name + ) for provider in self.app.providers: self.provider_dropdown.addItem(provider.provider_name) - self.provider_dropdown.setCurrentIndex(self.provider_dropdown.findText(current_provider)) + self.provider_dropdown.setCurrentIndex( + self.provider_dropdown.findText(current_provider) + ) content_layout.addWidget(self.provider_dropdown) # Add horizontal separator @@ -288,7 +329,10 @@ def init_ui(self): # Connect provider dropdown self.provider_dropdown.currentIndexChanged.connect( - lambda: self.init_provider_ui(self.app.providers[self.provider_dropdown.currentIndex()], self.provider_container) + lambda: self.init_provider_ui( + self.app.providers[self.provider_dropdown.currentIndex()], + self.provider_container, + ) ) # Add horizontal separator @@ -303,13 +347,19 @@ def init_ui(self): # Create bottom container for save button and restart notice bottom_container = QtWidgets.QWidget() - bottom_container.setStyleSheet("background: transparent;") # Ensure transparency + bottom_container.setStyleSheet( + "background: transparent;" + ) # Ensure transparency bottom_layout = QtWidgets.QVBoxLayout(bottom_container) - bottom_layout.setContentsMargins(30, 0, 30, 30) # Match content margins except top + bottom_layout.setContentsMargins( + 30, 0, 30, 30 + ) # Match content margins except top bottom_layout.setSpacing(10) # Add save button to bottom container - save_button = QtWidgets.QPushButton(_("Finish AI Setup") if self.providers_only else _("Save")) + save_button = QtWidgets.QPushButton( + _("Finish AI Setup") if self.providers_only else _("Save") + ) save_button.setStyleSheet(""" QPushButton { background-color: #4CAF50; @@ -327,12 +377,16 @@ def init_ui(self): bottom_layout.addWidget(save_button) if not self.providers_only: - restart_text = "" + \ - _("Please restart Writing Tools for changes to take effect.") + \ - "
" + restart_text = ( + "" + + _("Please restart Writing Tools for changes to take effect.") + + "
" + ) restart_notice = QtWidgets.QLabel(restart_text) - restart_notice.setStyleSheet(f"font-size: 15px; color: {'#cccccc' if colorMode == 'dark' else '#555555'}; font-style: italic;") + restart_notice.setStyleSheet( + f"font-size: 15px; color: {'#cccccc' if colorMode == 'dark' else '#555555'}; font-style: italic;" + ) restart_notice.setWordWrap(True) bottom_layout.addWidget(restart_notice) @@ -342,7 +396,9 @@ def init_ui(self): screen = QtWidgets.QApplication.primaryScreen().geometry() max_height = int(screen.height() * 0.85) # 85% of screen height desired_height = min(720, max_height) # Cap at 720px or 85% of screen height - self.resize(592, desired_height) # Use an exact width of 592px so stuff looks good! + self.resize( + 592, desired_height + ) # Use an exact width of 592px so stuff looks good! @staticmethod def toggle_autostart(state): @@ -351,23 +407,29 @@ def toggle_autostart(state): def save_settings(self): """Save the current settings.""" - self.app.config['locale'] = 'en' + self.app.config["locale"] = "en" if not self.providers_only: - self.app.config['shortcut'] = self.shortcut_input.text() - self.app.config['theme'] = 'gradient' if self.gradient_radio.isChecked() else 'plain' + self.app.config["shortcut"] = self.shortcut_input.text() + self.app.config["theme"] = ( + "gradient" if self.gradient_radio.isChecked() else "plain" + ) else: self.app.create_tray_icon() - self.app.config['streaming'] = False - self.app.config['provider'] = self.provider_dropdown.currentText() + self.app.config["streaming"] = False + self.app.config["provider"] = self.provider_dropdown.currentText() self.app.providers[self.provider_dropdown.currentIndex()].save_config() - provider_name = self.app.config.get('provider', 'Gemini') + provider_name = self.app.config.get("provider", "Gemini") self.app.current_provider = next( - (provider for provider in self.app.providers if provider.provider_name == provider_name), - self.app.providers[0] + ( + provider + for provider in self.app.providers + if provider.provider_name == provider_name + ), + self.app.providers[0], ) self.app.current_provider.load_config( @@ -382,4 +444,4 @@ def closeEvent(self, event): """Handle window close event.""" if self.providers_only: self.close_signal.emit() - super().closeEvent(event) \ No newline at end of file + super().closeEvent(event) diff --git a/Windows_and_Linux/ui/UIUtils.py b/Windows_and_Linux/ui/UIUtils.py index ec0e67a..ba616e2 100644 --- a/Windows_and_Linux/ui/UIUtils.py +++ b/Windows_and_Linux/ui/UIUtils.py @@ -5,7 +5,9 @@ from PySide6.QtGui import QImage, QPixmap import darkdetect -colorMode = 'dark' if darkdetect.isDark() else 'light' + +colorMode = "dark" if darkdetect.isDark() else "light" + class UIUtils: @classmethod @@ -13,8 +15,8 @@ def clear_layout(cls, layout): """ Clear the layout of all widgets. """ - while ((child := layout.takeAt(0)) != None): - #If the child is a layout, delete it + while (child := layout.takeAt(0)) != None: + # If the child is a layout, delete it if child.layout(): cls.clear_layout(child.layout()) child.layout().deleteLater() @@ -22,10 +24,12 @@ def clear_layout(cls, layout): child.widget().deleteLater() @classmethod - def resize_and_round_image(cls, image, image_size = 100, rounding_amount = 50): + def resize_and_round_image(cls, image, image_size=100, rounding_amount=50): image = image.scaledToWidth(image_size) clipPath = QtGui.QPainterPath() - clipPath.addRoundedRect(0, 0, image_size, image_size, rounding_amount, rounding_amount) + clipPath.addRoundedRect( + 0, 0, image_size, image_size, rounding_amount, rounding_amount + ) target = QImage(image_size, image_size, QImage.Format_ARGB32) target.fill(QtCore.Qt.GlobalColor.transparent) painter = QtGui.QPainter(target) @@ -39,11 +43,12 @@ def resize_and_round_image(cls, image, image_size = 100, rounding_amount = 50): @classmethod def setup_window_and_layout(cls, base: QtWidgets.QWidget): # Set the window icon - icon_path = os.path.join(os.path.dirname(sys.argv[0]), 'icons', 'app_icon.png') - if os.path.exists(icon_path): base.setWindowIcon(QtGui.QIcon(icon_path)) + icon_path = os.path.join(os.path.dirname(sys.argv[0]), "icons", "app_icon.png") + if os.path.exists(icon_path): + base.setWindowIcon(QtGui.QIcon(icon_path)) main_layout = QtWidgets.QVBoxLayout(base) main_layout.setContentsMargins(0, 0, 0, 0) - base.background = ThemeBackground(base, 'gradient') + base.background = ThemeBackground(base, "gradient") main_layout.addWidget(base.background) @@ -51,7 +56,8 @@ class ThemeBackground(QtWidgets.QWidget): """ A custom widget that creates a background for the application based on the selected theme. """ - def __init__(self, parent=None, theme='gradient', is_popup=False, border_radius=0): + + def __init__(self, parent=None, theme="gradient", is_popup=False, border_radius=0): super().__init__(parent) self.setAttribute(QtCore.Qt.WA_StyledBackground, True) self.theme = theme @@ -65,19 +71,40 @@ def paintEvent(self, event): painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) painter.setRenderHint(QtGui.QPainter.RenderHint.SmoothPixmapTransform, True) - if self.theme == 'gradient': + if self.theme == "gradient": if self.is_popup: - background_image = QtGui.QPixmap(os.path.join(os.path.dirname(sys.argv[0]), 'background_popup_dark.png' if colorMode == 'dark' else 'background_popup.png')) + background_image = QtGui.QPixmap( + os.path.join( + os.path.dirname(sys.argv[0]), + "background_popup_dark.png" + if colorMode == "dark" + else "background_popup.png", + ) + ) else: - background_image = QtGui.QPixmap(os.path.join(os.path.dirname(sys.argv[0]), 'background_dark.png' if colorMode == 'dark' else 'background.png')) + background_image = QtGui.QPixmap( + os.path.join( + os.path.dirname(sys.argv[0]), + "background_dark.png" + if colorMode == "dark" + else "background.png", + ) + ) # Adds a path/border using which the border radius would be drawn path = QtGui.QPainterPath() - path.addRoundedRect(0, 0, self.width(), self.height(), self.border_radius, self.border_radius) + path.addRoundedRect( + 0, + 0, + self.width(), + self.height(), + self.border_radius, + self.border_radius, + ) painter.setClipPath(path) painter.drawPixmap(self.rect(), background_image) else: - if colorMode == 'dark': + if colorMode == "dark": color = QtGui.QColor(35, 35, 35) # Dark mode color else: color = QtGui.QColor(222, 222, 222) # Light mode color @@ -86,4 +113,8 @@ def paintEvent(self, event): pen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0)) pen.setWidth(0) painter.setPen(pen) - painter.drawRoundedRect(QtCore.QRect(0, 0, self.width(), self.height()), self.border_radius, self.border_radius) + painter.drawRoundedRect( + QtCore.QRect(0, 0, self.width(), self.height()), + self.border_radius, + self.border_radius, + ) diff --git a/Windows_and_Linux/update_checker.py b/Windows_and_Linux/update_checker.py index 3591f68..c1e990a 100644 --- a/Windows_and_Linux/update_checker.py +++ b/Windows_and_Linux/update_checker.py @@ -8,10 +8,11 @@ UPDATE_CHECK_URL = "https://raw.githubusercontent.com/theJayTea/WritingTools/main/Windows_and_Linux/Latest_Version_for_Update_Check.txt" UPDATE_DOWNLOAD_URL = "https://github.com/theJayTea/WritingTools/releases" + class UpdateChecker: def __init__(self, app): self.app = app - + def _fetch_latest_version(self): """ Fetch the latest version number from GitHub. @@ -19,7 +20,7 @@ def _fetch_latest_version(self): """ try: with urlopen(UPDATE_CHECK_URL, timeout=5) as response: - data = response.read().decode('utf-8').strip() + data = response.read().decode("utf-8").strip() try: return int(data) except ValueError: @@ -45,30 +46,31 @@ def _retry_fetch_version(self): def check_updates(self): """ - Check if an update is available. + Check if an update is available. Always checks against cloud value and updates config accordingly. Returns True if an update is available. """ latest_version = self._retry_fetch_version() - + if latest_version is None: return False - + update_available = latest_version > CURRENT_VERSION - + # Always update config with fresh status if "update_available" in self.app.config or update_available: self.app.config["update_available"] = update_available self.app.save_config(self.app.config) - + return update_available def check_updates_async(self): """ Perform the update check in a background thread. """ + def check_thread(): self.check_updates() - + thread = threading.Thread(target=check_thread, daemon=True) - thread.start() \ No newline at end of file + thread.start() diff --git a/Windows_and_Linux/uv.lock b/Windows_and_Linux/uv.lock new file mode 100644 index 0000000..9992f88 --- /dev/null +++ b/Windows_and_Linux/uv.lock @@ -0,0 +1,962 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "altgraph" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418, upload-time = "2023-09-25T09:04:52.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212, upload-time = "2023-09-25T09:04:50.691Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "darkdetect" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/77/7575be73bf12dee231d0c6e60ce7fb7a7be4fcd58823374fc59a6e48262e/darkdetect-0.8.0.tar.gz", hash = "sha256:b5428e1170263eb5dea44c25dc3895edd75e6f52300986353cd63533fe7df8b1", size = 7681, upload-time = "2022-12-16T14:14:42.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl", hash = "sha256:a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85", size = 8955, upload-time = "2022-12-16T14:14:40.92Z" }, +] + +[[package]] +name = "dbus-next" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/45/6a40fbe886d60a8c26f480e7d12535502b5ba123814b3b9a0b002ebca198/dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5", size = 71112, upload-time = "2021-07-25T22:11:28.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fc/c0a3f4c4eaa5a22fbef91713474666e13d0ea2a69c84532579490a9f2cc8/dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b", size = 57885, upload-time = "2021-07-25T22:11:25.466Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "evdev" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/fe/a17c106a1f4061ce83f04d14bcedcfb2c38c7793ea56bfb906a6fadae8cb/evdev-1.9.2.tar.gz", hash = "sha256:5d3278892ce1f92a74d6bf888cc8525d9f68af85dbe336c95d1c87fb8f423069", size = 33301, upload-time = "2025-05-01T19:53:47.69Z" } + +[[package]] +name = "google-ai-generativelanguage" +version = "0.6.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/d1/48fe5d7a43d278e9f6b5ada810b0a3530bbeac7ed7fcbcd366f932f05316/google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3", size = 1375443, upload-time = "2025-01-13T21:50:47.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/a3/67b8a6ff5001a1d8864922f2d6488dc2a14367ceb651bc3f09a947f2f306/google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c", size = 1327356, upload-time = "2025-01-13T21:50:44.174Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-api-python-client" +version = "2.183.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/1f/49a2c83fc6dcd8b127cc9efbecf7d5fc36109c2028ba22ed6cb4d072fca4/google_api_python_client-2.183.0.tar.gz", hash = "sha256:abae37e04fecf719388e5c02f707ed9cdf952f10b217c79a3e76c636762e3ea9", size = 13645623, upload-time = "2025-09-23T22:27:00.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/06/1974f937172854bc7622eff5c2390f33542ceb843f305922922c8f5f7f17/google_api_python_client-2.183.0-py3-none-any.whl", hash = "sha256:2005b6e86c27be1db1a43f43e047a0f8e004159f3cceddecb08cf1624bddba31", size = 14214837, upload-time = "2025-09-23T22:26:57.758Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" }, +] + +[[package]] +name = "google-generativeai" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-ai-generativelanguage" }, + { name = "google-api-core" }, + { name = "google-api-python-client" }, + { name = "google-auth" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/40/c42ff9ded9f09ec9392879a8e6538a00b2dc185e834a3392917626255419/google_generativeai-0.8.5-py3-none-any.whl", hash = "sha256:22b420817fb263f8ed520b33285f45976d5b21e904da32b80d4fd20c055123a2", size = 155427, upload-time = "2025-04-17T00:40:00.67Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, +] + +[[package]] +name = "grpcio" +version = "1.75.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/f7/8963848164c7604efb3a3e6ee457fdb3a469653e19002bd24742473254f8/grpcio-1.75.1.tar.gz", hash = "sha256:3e81d89ece99b9ace23a6916880baca613c03a799925afb2857887efa8b1b3d2", size = 12731327, upload-time = "2025-09-26T09:03:36.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/81/42be79e73a50aaa20af66731c2defeb0e8c9008d9935a64dd8ea8e8c44eb/grpcio-1.75.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:7b888b33cd14085d86176b1628ad2fcbff94cfbbe7809465097aa0132e58b018", size = 5668314, upload-time = "2025-09-26T09:01:55.424Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/3686ed15822fedc58c22f82b3a7403d9faf38d7c33de46d4de6f06e49426/grpcio-1.75.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:8775036efe4ad2085975531d221535329f5dac99b6c2a854a995456098f99546", size = 11476125, upload-time = "2025-09-26T09:01:57.927Z" }, + { url = "https://files.pythonhosted.org/packages/14/85/21c71d674f03345ab183c634ecd889d3330177e27baea8d5d247a89b6442/grpcio-1.75.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb658f703468d7fbb5dcc4037c65391b7dc34f808ac46ed9136c24fc5eeb041d", size = 6246335, upload-time = "2025-09-26T09:02:00.76Z" }, + { url = "https://files.pythonhosted.org/packages/fd/db/3beb661bc56a385ae4fa6b0e70f6b91ac99d47afb726fe76aaff87ebb116/grpcio-1.75.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b7177a1cdb3c51b02b0c0a256b0a72fdab719600a693e0e9037949efffb200b", size = 6916309, upload-time = "2025-09-26T09:02:02.894Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9c/eda9fe57f2b84343d44c1b66cf3831c973ba29b078b16a27d4587a1fdd47/grpcio-1.75.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d4fa6ccc3ec2e68a04f7b883d354d7fea22a34c44ce535a2f0c0049cf626ddf", size = 6435419, upload-time = "2025-09-26T09:02:05.055Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b8/090c98983e0a9d602e3f919a6e2d4e470a8b489452905f9a0fa472cac059/grpcio-1.75.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d86880ecaeb5b2f0a8afa63824de93adb8ebe4e49d0e51442532f4e08add7d6", size = 7064893, upload-time = "2025-09-26T09:02:07.275Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c0/6d53d4dbbd00f8bd81571f5478d8a95528b716e0eddb4217cc7cb45aae5f/grpcio-1.75.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a8041d2f9e8a742aeae96f4b047ee44e73619f4f9d24565e84d5446c623673b6", size = 8011922, upload-time = "2025-09-26T09:02:09.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7c/48455b2d0c5949678d6982c3e31ea4d89df4e16131b03f7d5c590811cbe9/grpcio-1.75.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3652516048bf4c314ce12be37423c79829f46efffb390ad64149a10c6071e8de", size = 7466181, upload-time = "2025-09-26T09:02:12.279Z" }, + { url = "https://files.pythonhosted.org/packages/fd/12/04a0e79081e3170b6124f8cba9b6275871276be06c156ef981033f691880/grpcio-1.75.1-cp312-cp312-win32.whl", hash = "sha256:44b62345d8403975513af88da2f3d5cc76f73ca538ba46596f92a127c2aea945", size = 3938543, upload-time = "2025-09-26T09:02:14.77Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d7/11350d9d7fb5adc73d2b0ebf6ac1cc70135577701e607407fe6739a90021/grpcio-1.75.1-cp312-cp312-win_amd64.whl", hash = "sha256:b1e191c5c465fa777d4cafbaacf0c01e0d5278022082c0abbd2ee1d6454ed94d", size = 4641938, upload-time = "2025-09-26T09:02:16.927Z" }, + { url = "https://files.pythonhosted.org/packages/46/74/bac4ab9f7722164afdf263ae31ba97b8174c667153510322a5eba4194c32/grpcio-1.75.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:3bed22e750d91d53d9e31e0af35a7b0b51367e974e14a4ff229db5b207647884", size = 5672779, upload-time = "2025-09-26T09:02:19.11Z" }, + { url = "https://files.pythonhosted.org/packages/a6/52/d0483cfa667cddaa294e3ab88fd2c2a6e9dc1a1928c0e5911e2e54bd5b50/grpcio-1.75.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5b8f381eadcd6ecaa143a21e9e80a26424c76a0a9b3d546febe6648f3a36a5ac", size = 11470623, upload-time = "2025-09-26T09:02:22.117Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e4/d1954dce2972e32384db6a30273275e8c8ea5a44b80347f9055589333b3f/grpcio-1.75.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5bf4001d3293e3414d0cf99ff9b1139106e57c3a66dfff0c5f60b2a6286ec133", size = 6248838, upload-time = "2025-09-26T09:02:26.426Z" }, + { url = "https://files.pythonhosted.org/packages/06/43/073363bf63826ba8077c335d797a8d026f129dc0912b69c42feaf8f0cd26/grpcio-1.75.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f82ff474103e26351dacfe8d50214e7c9322960d8d07ba7fa1d05ff981c8b2d", size = 6922663, upload-time = "2025-09-26T09:02:28.724Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6f/076ac0df6c359117676cacfa8a377e2abcecec6a6599a15a672d331f6680/grpcio-1.75.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ee119f4f88d9f75414217823d21d75bfe0e6ed40135b0cbbfc6376bc9f7757d", size = 6436149, upload-time = "2025-09-26T09:02:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/6b/27/1d08824f1d573fcb1fa35ede40d6020e68a04391709939e1c6f4193b445f/grpcio-1.75.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:664eecc3abe6d916fa6cf8dd6b778e62fb264a70f3430a3180995bf2da935446", size = 7067989, upload-time = "2025-09-26T09:02:33.233Z" }, + { url = "https://files.pythonhosted.org/packages/c6/98/98594cf97b8713feb06a8cb04eeef60b4757e3e2fb91aa0d9161da769843/grpcio-1.75.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c32193fa08b2fbebf08fe08e84f8a0aad32d87c3ad42999c65e9449871b1c66e", size = 8010717, upload-time = "2025-09-26T09:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7e/bb80b1bba03c12158f9254762cdf5cced4a9bc2e8ed51ed335915a5a06ef/grpcio-1.75.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5cebe13088b9254f6e615bcf1da9131d46cfa4e88039454aca9cb65f639bd3bc", size = 7463822, upload-time = "2025-09-26T09:02:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/23/1c/1ea57fdc06927eb5640f6750c697f596f26183573069189eeaf6ef86ba2d/grpcio-1.75.1-cp313-cp313-win32.whl", hash = "sha256:4b4c678e7ed50f8ae8b8dbad15a865ee73ce12668b6aaf411bf3258b5bc3f970", size = 3938490, upload-time = "2025-09-26T09:02:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/4b/24/fbb8ff1ccadfbf78ad2401c41aceaf02b0d782c084530d8871ddd69a2d49/grpcio-1.75.1-cp313-cp313-win_amd64.whl", hash = "sha256:5573f51e3f296a1bcf71e7a690c092845fb223072120f4bdb7a5b48e111def66", size = 4642538, upload-time = "2025-09-26T09:02:42.519Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/9a0a5cecd24302b9fdbcd55d15ed6267e5f3d5b898ff9ac8cbe17ee76129/grpcio-1.75.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:c05da79068dd96723793bffc8d0e64c45f316248417515f28d22204d9dae51c7", size = 5673319, upload-time = "2025-09-26T09:02:44.742Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ec/9d6959429a83fbf5df8549c591a8a52bb313976f6646b79852c4884e3225/grpcio-1.75.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06373a94fd16ec287116a825161dca179a0402d0c60674ceeec8c9fba344fe66", size = 11480347, upload-time = "2025-09-26T09:02:47.539Z" }, + { url = "https://files.pythonhosted.org/packages/09/7a/26da709e42c4565c3d7bf999a9569da96243ce34a8271a968dee810a7cf1/grpcio-1.75.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4484f4b7287bdaa7a5b3980f3c7224c3c622669405d20f69549f5fb956ad0421", size = 6254706, upload-time = "2025-09-26T09:02:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/f1/08/dcb26a319d3725f199c97e671d904d84ee5680de57d74c566a991cfab632/grpcio-1.75.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2720c239c1180eee69f7883c1d4c83fc1a495a2535b5fa322887c70bf02b16e8", size = 6922501, upload-time = "2025-09-26T09:02:52.711Z" }, + { url = "https://files.pythonhosted.org/packages/78/66/044d412c98408a5e23cb348845979a2d17a2e2b6c3c34c1ec91b920f49d0/grpcio-1.75.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:07a554fa31c668cf0e7a188678ceeca3cb8fead29bbe455352e712ec33ca701c", size = 6437492, upload-time = "2025-09-26T09:02:55.542Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9d/5e3e362815152aa1afd8b26ea613effa005962f9da0eec6e0e4527e7a7d1/grpcio-1.75.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3e71a2105210366bfc398eef7f57a664df99194f3520edb88b9c3a7e46ee0d64", size = 7081061, upload-time = "2025-09-26T09:02:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1a/46615682a19e100f46e31ddba9ebc297c5a5ab9ddb47b35443ffadb8776c/grpcio-1.75.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8679aa8a5b67976776d3c6b0521e99d1c34db8a312a12bcfd78a7085cb9b604e", size = 8010849, upload-time = "2025-09-26T09:03:00.548Z" }, + { url = "https://files.pythonhosted.org/packages/67/8e/3204b94ac30b0f675ab1c06540ab5578660dc8b690db71854d3116f20d00/grpcio-1.75.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aad1c774f4ebf0696a7f148a56d39a3432550612597331792528895258966dc0", size = 7464478, upload-time = "2025-09-26T09:03:03.096Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/2d90652b213863b2cf466d9c1260ca7e7b67a16780431b3eb1d0420e3d5b/grpcio-1.75.1-cp314-cp314-win32.whl", hash = "sha256:62ce42d9994446b307649cb2a23335fa8e927f7ab2cbf5fcb844d6acb4d85f9c", size = 4012672, upload-time = "2025-09-26T09:03:05.477Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/e2e6e9fc1c985cd1a59e6996a05647c720fe8a03b92f5ec2d60d366c531e/grpcio-1.75.1-cp314-cp314-win_amd64.whl", hash = "sha256:f86e92275710bea3000cb79feca1762dc0ad3b27830dd1a74e82ab321d4ee464", size = 4772475, upload-time = "2025-09-26T09:03:07.661Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.71.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/58/317b0134129b556a93a3b0afe00ee675b5657f0155509e22fcb853bafe2d/grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3", size = 14424, upload-time = "2025-06-28T04:23:42.136Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httplib2" +version = "0.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/77/6653db69c1f7ecfe5e3f9726fdadc981794656fcd7d98c4209fecfea9993/httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c", size = 250759, upload-time = "2025-09-11T12:16:03.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/a2/0d269db0f6163be503775dc8b6a6fa15820cc9fdc866f6ba608d86b721f2/httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24", size = 91148, upload-time = "2025-09-11T12:16:01.803Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "jiter" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/b5/3009b112b8f673e568ef79af9863d8309a15f0a8cdcc06ed6092051f377e/jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada", size = 305510, upload-time = "2025-09-15T09:19:25.893Z" }, + { url = "https://files.pythonhosted.org/packages/fe/82/15514244e03b9e71e086bbe2a6de3e4616b48f07d5f834200c873956fb8c/jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99", size = 316521, upload-time = "2025-09-15T09:19:27.525Z" }, + { url = "https://files.pythonhosted.org/packages/92/94/7a2e905f40ad2d6d660e00b68d818f9e29fb87ffe82774f06191e93cbe4a/jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6", size = 338214, upload-time = "2025-09-15T09:19:28.727Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9c/5791ed5bdc76f12110158d3316a7a3ec0b1413d018b41c5ed399549d3ad5/jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1", size = 361280, upload-time = "2025-09-15T09:19:30.013Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7f/b7d82d77ff0d2cb06424141000176b53a9e6b16a1125525bb51ea4990c2e/jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4", size = 487895, upload-time = "2025-09-15T09:19:31.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/44/10a1475d46f1fc1fd5cc2e82c58e7bca0ce5852208e0fa5df2f949353321/jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72", size = 378421, upload-time = "2025-09-15T09:19:32.746Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5f/0dc34563d8164d31d07bc09d141d3da08157a68dcd1f9b886fa4e917805b/jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591", size = 347932, upload-time = "2025-09-15T09:19:34.612Z" }, + { url = "https://files.pythonhosted.org/packages/f7/de/b68f32a4fcb7b4a682b37c73a0e5dae32180140cd1caf11aef6ad40ddbf2/jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09", size = 386959, upload-time = "2025-09-15T09:19:35.994Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/c08c92e713b6e28972a846a81ce374883dac2f78ec6f39a0dad9f2339c3a/jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5", size = 517187, upload-time = "2025-09-15T09:19:37.426Z" }, + { url = "https://files.pythonhosted.org/packages/89/b5/4a283bec43b15aad54fcae18d951f06a2ec3f78db5708d3b59a48e9c3fbd/jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206", size = 509461, upload-time = "2025-09-15T09:19:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/f8bad793010534ea73c985caaeef8cc22dfb1fedb15220ecdf15c623c07a/jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b", size = 206664, upload-time = "2025-09-15T09:19:40.096Z" }, + { url = "https://files.pythonhosted.org/packages/ed/42/5823ec2b1469395a160b4bf5f14326b4a098f3b6898fbd327366789fa5d3/jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c", size = 203520, upload-time = "2025-09-15T09:19:41.798Z" }, + { url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb", size = 305021, upload-time = "2025-09-15T09:19:43.523Z" }, + { url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471", size = 314384, upload-time = "2025-09-15T09:19:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd", size = 337389, upload-time = "2025-09-15T09:19:46.094Z" }, + { url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921", size = 360519, upload-time = "2025-09-15T09:19:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df", size = 487198, upload-time = "2025-09-15T09:19:49.116Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982", size = 377835, upload-time = "2025-09-15T09:19:50.468Z" }, + { url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64", size = 347655, upload-time = "2025-09-15T09:19:51.726Z" }, + { url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1", size = 386135, upload-time = "2025-09-15T09:19:53.075Z" }, + { url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758", size = 516063, upload-time = "2025-09-15T09:19:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166", size = 508139, upload-time = "2025-09-15T09:19:55.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80", size = 206369, upload-time = "2025-09-15T09:19:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6", size = 202538, upload-time = "2025-09-15T09:19:58.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33", size = 313737, upload-time = "2025-09-15T09:19:59.638Z" }, + { url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03", size = 346183, upload-time = "2025-09-15T09:20:01.442Z" }, + { url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba", size = 204225, upload-time = "2025-09-15T09:20:03.102Z" }, + { url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72", size = 304414, upload-time = "2025-09-15T09:20:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774", size = 314223, upload-time = "2025-09-15T09:20:05.631Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0", size = 337306, upload-time = "2025-09-15T09:20:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a", size = 360565, upload-time = "2025-09-15T09:20:08.283Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773", size = 486465, upload-time = "2025-09-15T09:20:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7", size = 377581, upload-time = "2025-09-15T09:20:10.884Z" }, + { url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2", size = 347102, upload-time = "2025-09-15T09:20:12.175Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2", size = 386477, upload-time = "2025-09-15T09:20:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0", size = 516004, upload-time = "2025-09-15T09:20:14.848Z" }, + { url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73", size = 507855, upload-time = "2025-09-15T09:20:16.176Z" }, + { url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2", size = 205802, upload-time = "2025-09-15T09:20:17.661Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40", size = 313405, upload-time = "2025-09-15T09:20:18.918Z" }, + { url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309, upload-time = "2023-09-25T09:10:16.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, +] + +[[package]] +name = "markdown2" +version = "2.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/f8/b2ae8bf5f28f9b510ae097415e6e4cb63226bb28d7ee01aec03a755ba03b/markdown2-2.5.4.tar.gz", hash = "sha256:a09873f0b3c23dbfae589b0080587df52ad75bb09a5fa6559147554736676889", size = 145652, upload-time = "2025-07-27T16:16:24.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/06/2697b5043c3ecb720ce0d243fc7cf5024c0b5b1e450506e9b21939019963/markdown2-2.5.4-py3-none-any.whl", hash = "sha256:3c4b2934e677be7fec0e6f2de4410e116681f4ad50ec8e5ba7557be506d3f439", size = 49954, upload-time = "2025-07-27T16:16:23.026Z" }, +] + +[[package]] +name = "ollama" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/47/f9ee32467fe92744474a8c72e138113f3b529fc266eea76abfdec9a33f3b/ollama-0.6.0.tar.gz", hash = "sha256:da2b2d846b5944cfbcee1ca1e6ee0585f6c9d45a2fe9467cbcd096a37383da2f", size = 50811, upload-time = "2025-09-24T22:46:02.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/c1/edc9f41b425ca40b26b7c104c5f6841a4537bb2552bfa6ca66e81405bb95/ollama-0.6.0-py3-none-any.whl", hash = "sha256:534511b3ccea2dff419ae06c3b58d7f217c55be7897c8ce5868dfb6b219cf7a0", size = 14130, upload-time = "2025-09-24T22:46:01.19Z" }, +] + +[[package]] +name = "openai" +version = "1.109.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pefile" +version = "2023.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854, upload-time = "2023-02-07T12:23:55.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, +] + +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226, upload-time = "2025-05-28T23:51:59.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963, upload-time = "2025-05-28T23:51:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818, upload-time = "2025-05-28T23:51:44.297Z" }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091, upload-time = "2025-05-28T23:51:45.907Z" }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824, upload-time = "2025-05-28T23:51:47.545Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942, upload-time = "2025-05-28T23:51:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/94/1f62e95e4a28b64cfbb5b922ef3046f968b47170d37a1e1a029f56ac9cb4/pyinstaller-6.16.0.tar.gz", hash = "sha256:53559fe1e041a234f2b4dcc3288ea8bdd57f7cad8a6644e422c27bb407f3edef", size = 4008473, upload-time = "2025-09-13T20:07:01.733Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/0a/c42ce6e5d3de287f2e9432a074fb209f1fb72a86a72f3903849fdb5e4829/pyinstaller-6.16.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:7fd1c785219a87ca747c21fa92f561b0d2926a7edc06d0a0fe37f3736e00bd7a", size = 1027899, upload-time = "2025-09-13T20:05:59.2Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d0/f18fedde32835d5a758f464c75924e2154065625f09d5456c3c303527654/pyinstaller-6.16.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b756ddb9007b8141c5476b553351f9d97559b8af5d07f9460869bfae02be26b0", size = 727990, upload-time = "2025-09-13T20:06:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/7a/db/c8bb47514ce857b24bf9294cf1ff74844b6a489fa0ab4ef6f923288c4e38/pyinstaller-6.16.0-py3-none-manylinux2014_i686.whl", hash = "sha256:0a48f55b85ff60f83169e10050f2759019cf1d06773ad1c4da3a411cd8751058", size = 739238, upload-time = "2025-09-13T20:06:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/451dc784a8fcca0fe9f9b6b802d58555364a95b60f253613a2c83fc6b023/pyinstaller-6.16.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:73ba72e04fcece92e32518bbb1e1fb5ac2892677943dfdff38e01a06e8742851", size = 737142, upload-time = "2025-09-13T20:06:11.732Z" }, + { url = "https://files.pythonhosted.org/packages/71/37/2f457479ef8fa2821cdb448acee2421dfb19fbe908bf5499d1930c164084/pyinstaller-6.16.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b1752488248f7899281b17ca3238eefb5410521291371a686a4f5830f29f52b3", size = 734133, upload-time = "2025-09-13T20:06:15.477Z" }, + { url = "https://files.pythonhosted.org/packages/63/c4/0f7daac4d062a4d1ac2571d8a8b9b5d6812094fcd914d139af591ca5e1ba/pyinstaller-6.16.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ba618a61627ee674d6d68e5de084ba17c707b59a4f2a856084b3999bdffbd3f0", size = 733817, upload-time = "2025-09-13T20:06:19.683Z" }, + { url = "https://files.pythonhosted.org/packages/11/e4/b6127265b42bef883e8873d850becadf748bc5652e5a7029b059328f3c31/pyinstaller-6.16.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c8b7ef536711617e12fef4673806198872033fa06fa92326ad7fd1d84a9fa454", size = 732912, upload-time = "2025-09-13T20:06:23.46Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/c6663107bdf814b2916e71563beabd09f693c47712213bc228994cb2cc65/pyinstaller-6.16.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1ebf84d02c51fed19b82a8abb4df536923abd55bb684d694e1356e4ae2a0ce5", size = 732773, upload-time = "2025-09-13T20:06:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/a3/14/cabe9bc5f60b95d2e70e7d045ab94b0015ff8f6c8b16e2142d3597e30749/pyinstaller-6.16.0-py3-none-win32.whl", hash = "sha256:6d5f8617f3650ff9ef893e2ab4ddbf3c0d23d0c602ef74b5df8fbef4607840c8", size = 1313878, upload-time = "2025-09-13T20:06:33.234Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/2005efbc297e7813c1d6f18484aa94a1a81ce87b6a5b497c563681f4c4ea/pyinstaller-6.16.0-py3-none-win_amd64.whl", hash = "sha256:bc10eb1a787f99fea613509f55b902fbd2d8b73ff5f51ff245ea29a481d97d41", size = 1374706, upload-time = "2025-09-13T20:06:39.95Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f4/4dfcf69b86d60fcaae05a42bbff1616d48a91e71726e5ed795d773dae9b3/pyinstaller-6.16.0-py3-none-win_arm64.whl", hash = "sha256:d0af8a401de792c233c32c44b16d065ca9ab8262ee0c906835c12bdebc992a64", size = 1315923, upload-time = "2025-09-13T20:06:45.846Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2025.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/83/be0f57c0b77b66c33c2283ebd4ea341022b5a743e97c5fb3bebab82b38b9/pyinstaller_hooks_contrib-2025.9.tar.gz", hash = "sha256:56e972bdaad4e9af767ed47d132362d162112260cbe488c9da7fee01f228a5a6", size = 165189, upload-time = "2025-09-24T11:21:35.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/26/23b4cfc77d7f808c69f59070e1e8293a579ec281a547c61562357160b346/pyinstaller_hooks_contrib-2025.9-py3-none-any.whl", hash = "sha256:ccbfaa49399ef6b18486a165810155e5a8d4c59b41f20dc5da81af7482aaf038", size = 444283, upload-time = "2025-09-24T11:21:33.67Z" }, +] + +[[package]] +name = "pynput" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "evdev", marker = "'linux' in sys_platform" }, + { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "python-xlib", marker = "'linux' in sys_platform" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/c3/dccf44c68225046df5324db0cc7d563a560635355b3e5f1d249468268a6f/pynput-1.8.1.tar.gz", hash = "sha256:70d7c8373ee98911004a7c938742242840a5628c004573d84ba849d4601df81e", size = 82289, upload-time = "2025-03-17T17:12:01.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/4f/ac3fa906ae8a375a536b12794128c5efacade9eaa917a35dfd27ce0c7400/pynput-1.8.1-py2.py3-none-any.whl", hash = "sha256:42dfcf27404459ca16ca889c8fb8ffe42a9fe54f722fd1a3e130728e59e768d2", size = 91693, upload-time = "2025-03-17T17:12:00.094Z" }, +] + +[[package]] +name = "pyobjc-core" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe", size = 974602, upload-time = "2025-06-14T20:56:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/09/e83228e878e73bf756749939f906a872da54488f18d75658afa7f1abbab1/pyobjc_core-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:765b97dea6b87ec4612b3212258024d8496ea23517c95a1c5f0735f96b7fd529", size = 677985, upload-time = "2025-06-14T20:44:48.375Z" }, + { url = "https://files.pythonhosted.org/packages/c5/24/12e4e2dae5f85fd0c0b696404ed3374ea6ca398e7db886d4f1322eb30799/pyobjc_core-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18986f83998fbd5d3f56d8a8428b2f3e0754fd15cef3ef786ca0d29619024f2c", size = 676431, upload-time = "2025-06-14T20:44:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/f7/79/031492497624de4c728f1857181b06ce8c56444db4d49418fa459cba217c/pyobjc_core-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8849e78cfe6595c4911fbba29683decfb0bf57a350aed8a43316976ba6f659d2", size = 719330, upload-time = "2025-06-14T20:44:51.621Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7d/6169f16a0c7ec15b9381f8bf33872baf912de2ef68d96c798ca4c6ee641f/pyobjc_core-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8cb9ed17a8d84a312a6e8b665dd22393d48336ea1d8277e7ad20c19a38edf731", size = 667203, upload-time = "2025-06-14T20:44:53.262Z" }, + { url = "https://files.pythonhosted.org/packages/49/0f/f5ab2b0e57430a3bec9a62b6153c0e79c05a30d77b564efdb9f9446eeac5/pyobjc_core-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:f2455683e807f8541f0d83fbba0f5d9a46128ab0d5cc83ea208f0bec759b7f96", size = 708807, upload-time = "2025-06-14T20:44:54.851Z" }, +] + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/3f/b33ce0cecc3a42f6c289dcbf9ff698b0d9e85f5796db2e9cb5dadccffbb9/pyobjc_framework_applicationservices-11.1.tar.gz", hash = "sha256:03fcd8c0c600db98fa8b85eb7b3bc31491701720c795e3f762b54e865138bbaf", size = 224842, upload-time = "2025-06-14T20:56:40.648Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/ec/46a5c710e2d7edf55105223c34fed5a7b7cc7aba7d00a3a7b0405d6a2d1a/pyobjc_framework_applicationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f4a85ccd78bab84f7f05ac65ff9be117839dfc09d48c39edd65c617ed73eb01c", size = 31056, upload-time = "2025-06-14T20:45:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/c4/06/c2a309e6f37bfa73a2a581d3301321b2033e25b249e2a01e417a3c34e799/pyobjc_framework_applicationservices-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:385a89f4d0838c97a331e247519d9e9745aa3f7427169d18570e3c664076a63c", size = 31072, upload-time = "2025-06-14T20:45:19.707Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/357bf498c27f1b4d48385860d8374b2569adc1522aabe32befd77089c070/pyobjc_framework_applicationservices-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f480fab20f3005e559c9d06c9a3874a1f1c60dde52c6d28a53ab59b45e79d55f", size = 31335, upload-time = "2025-06-14T20:45:20.462Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b6/797fdd81399fe8251196f29a621ba3f3f04d5c579d95fd304489f5558202/pyobjc_framework_applicationservices-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:e8dee91c6a14fd042f98819dc0ac4a182e0e816282565534032f0e544bfab143", size = 31196, upload-time = "2025-06-14T20:45:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/68/45/47eba8d7cdf16d778240ed13fb405e8d712464170ed29d0463363a695194/pyobjc_framework_applicationservices-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a0ce40a57a9b993793b6f72c4fd93f80618ef54a69d76a1da97b8360a2f3ffc5", size = 31446, upload-time = "2025-06-14T20:45:22.313Z" }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/da/41c0f7edc92ead461cced7e67813e27fa17da3c5da428afdb4086c69d7ba/pyobjc_framework_cocoa-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806de56f06dfba8f301a244cce289d54877c36b4b19818e3b53150eb7c2424d0", size = 388983, upload-time = "2025-06-14T20:46:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/a01477cde2a040f97e226f3e15e5ffd1268fcb6d1d664885a95ba592eca9/pyobjc_framework_cocoa-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54e93e1d9b0fc41c032582a6f0834befe1d418d73893968f3f450281b11603da", size = 389049, upload-time = "2025-06-14T20:46:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/64cf2661f6ab7c124d0486ec6d1d01a9bb2838a0d2a46006457d8c5e6845/pyobjc_framework_cocoa-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fd5245ee1997d93e78b72703be1289d75d88ff6490af94462b564892e9266350", size = 393110, upload-time = "2025-06-14T20:46:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/33/87/01e35c5a3c5bbdc93d5925366421e10835fcd7b23347b6c267df1b16d0b3/pyobjc_framework_cocoa-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:aede53a1afc5433e1e7d66568cc52acceeb171b0a6005407a42e8e82580b4fc0", size = 392644, upload-time = "2025-06-14T20:46:56.503Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7c/54afe9ffee547c41e1161691e72067a37ed27466ac71c089bfdcd07ca70d/pyobjc_framework_cocoa-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:1b5de4e1757bb65689d6dc1f8d8717de9ec8587eb0c4831c134f13aba29f9b71", size = 396742, upload-time = "2025-06-14T20:46:57.64Z" }, +] + +[[package]] +name = "pyobjc-framework-coretext" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/e9/d3231c4f87d07b8525401fd6ad3c56607c9e512c5490f0a7a6abb13acab6/pyobjc_framework_coretext-11.1.tar.gz", hash = "sha256:a29bbd5d85c77f46a8ee81d381b847244c88a3a5a96ac22f509027ceceaffaf6", size = 274702, upload-time = "2025-06-14T20:57:16.059Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/67/9cc5189c366e67dc3e5b5976fac73cc6405841095f795d3fa0d5fc43d76a/pyobjc_framework_coretext-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1597bf7234270ee1b9963bf112e9061050d5fb8e1384b3f50c11bde2fe2b1570", size = 30175, upload-time = "2025-06-14T20:48:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d1/6ec2ef4f8133177203a742d5db4db90bbb3ae100aec8d17f667208da84c9/pyobjc_framework_coretext-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:37e051e8f12a0f47a81b8efc8c902156eb5bc3d8123c43e5bd4cebd24c222228", size = 30180, upload-time = "2025-06-14T20:48:35.766Z" }, + { url = "https://files.pythonhosted.org/packages/0a/84/d4a95e49f6af59503ba257fbed0471b6932f0afe8b3725c018dd3ba40150/pyobjc_framework_coretext-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:56a3a02202e0d50be3c43e781c00f9f1859ab9b73a8342ff56260b908e911e37", size = 30768, upload-time = "2025-06-14T20:48:36.869Z" }, + { url = "https://files.pythonhosted.org/packages/64/4c/16e1504e06a5cb23eec6276835ddddb087637beba66cf84b5c587eba99be/pyobjc_framework_coretext-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:15650ba99692d00953e91e53118c11636056a22c90d472020f7ba31500577bf5", size = 30155, upload-time = "2025-06-14T20:48:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a4/cbfa9c874b2770fb1ba5c38c42b0e12a8b5aa177a5a86d0ad49b935aa626/pyobjc_framework_coretext-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:fb27f66a56660c31bb956191d64b85b95bac99cfb833f6e99622ca0ac4b3ba12", size = 30768, upload-time = "2025-06-14T20:48:38.734Z" }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/37/ee6e0bdd31b3b277fec00e5ee84d30eb1b5b8b0e025095e24ddc561697d0/pyobjc_framework_quartz-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ac806067541917d6119b98d90390a6944e7d9bd737f5c0a79884202327c9204", size = 216410, upload-time = "2025-06-14T20:53:36.346Z" }, + { url = "https://files.pythonhosted.org/packages/bd/27/4f4fc0e6a0652318c2844608dd7c41e49ba6006ee5fb60c7ae417c338357/pyobjc_framework_quartz-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43a1138280571bbf44df27a7eef519184b5c4183a588598ebaaeb887b9e73e76", size = 216816, upload-time = "2025-06-14T20:53:37.358Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8a/1d15e42496bef31246f7401aad1ebf0f9e11566ce0de41c18431715aafbc/pyobjc_framework_quartz-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b23d81c30c564adf6336e00b357f355b35aad10075dd7e837cfd52a9912863e5", size = 221941, upload-time = "2025-06-14T20:53:38.34Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/a3f84d06e567efc12c104799c7fd015f9bea272a75f799eda8b79e8163c6/pyobjc_framework_quartz-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:07cbda78b4a8fcf3a2d96e047a2ff01f44e3e1820f46f0f4b3b6d77ff6ece07c", size = 221312, upload-time = "2025-06-14T20:53:39.435Z" }, + { url = "https://files.pythonhosted.org/packages/76/ef/8c08d4f255bb3efe8806609d1f0b1ddd29684ab0f9ffb5e26d3ad7957b29/pyobjc_framework_quartz-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:39d02a3df4b5e3eee1e0da0fb150259476910d2a9aa638ab94153c24317a9561", size = 226353, upload-time = "2025-06-14T20:53:40.655Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + +[[package]] +name = "pyside6" +version = "6.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-addons" }, + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/42/43577413bd5ab26f5f21e7a43c9396aac158a5d01900c87e4609c0e96278/pyside6-6.9.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:71245c76bfbe5c41794ffd8546730ec7cc869d4bbe68535639e026e4ef8a7714", size = 558102, upload-time = "2025-08-26T07:52:57.302Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/cb84f802df3dcc1d196d2f9f37dbb8227761826f936987c9386b8ae1ffcc/pyside6-6.9.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:64a9e2146e207d858e00226f68d7c1b4ab332954742a00dcabb721bb9e4aa0cd", size = 558243, upload-time = "2025-08-26T07:52:59.272Z" }, + { url = "https://files.pythonhosted.org/packages/94/2d/715db9da437b4632d06e2c4718aee9937760b84cf36c23d5441989e581b0/pyside6-6.9.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a78fad16241a1f2ed0fa0098cf3d621f591fc75b4badb7f3fa3959c9d861c806", size = 558245, upload-time = "2025-08-26T07:53:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/2e75cbff0e17f16b83d2b7e8434ae9175cae8d6ff816c9b56d307cf53c86/pyside6-6.9.2-cp39-abi3-win_amd64.whl", hash = "sha256:d1afbf48f9a5612b9ee2dc7c384c1a65c08b5830ba5e7d01f66d82678e5459df", size = 564604, upload-time = "2025-08-26T07:53:02.402Z" }, + { url = "https://files.pythonhosted.org/packages/dc/34/e3dd4e046673efcbcfbe0aa2760df06b2877739b8f4da60f0229379adebd/pyside6-6.9.2-cp39-abi3-win_arm64.whl", hash = "sha256:1499b1d7629ab92119118e2636b4ace836b25e457ddf01003fdca560560b8c0a", size = 401833, upload-time = "2025-08-26T07:53:03.742Z" }, +] + +[[package]] +name = "pyside6-addons" +version = "6.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/39/a8f4a55001b6a0aaee042e706de2447f21c6dc2a610f3d3debb7d04db821/pyside6_addons-6.9.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:7019fdcc0059626eb1608b361371f4dc8cb7f2d02f066908fd460739ff5a07cd", size = 316693692, upload-time = "2025-08-26T07:33:31.529Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/0b16e9dabd4cafe02d59531832bc30b6f0e14c92076e90dd02379d365cb2/pyside6_addons-6.9.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:24350e5415317f269e743d1f7b4933fe5f59d90894aa067676c9ce6bfe9e7988", size = 166984613, upload-time = "2025-08-26T07:33:47.569Z" }, + { url = "https://files.pythonhosted.org/packages/f4/55/dc42a73387379bae82f921b7659cd2006ec0e80f7052f83ddc07e9eb9cca/pyside6_addons-6.9.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:af8dee517de8d336735a6543f7dd496eb580e852c14b4d2304b890e2a29de499", size = 162908466, upload-time = "2025-08-26T07:39:49.331Z" }, + { url = "https://files.pythonhosted.org/packages/14/fa/396a2e86230c493b565e2dc89dc64e4b1c63582ac69afe77b693c3817a53/pyside6_addons-6.9.2-cp39-abi3-win_amd64.whl", hash = "sha256:98d2413904ee4b2b754b077af7875fa6ec08468c01a6628a2c9c3d2cece4874f", size = 160216647, upload-time = "2025-08-26T07:42:18.903Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fe/25f61259f1d5ec4648c9f6d2abd8e2cba2188f10735a57abafda719958e5/pyside6_addons-6.9.2-cp39-abi3-win_arm64.whl", hash = "sha256:b430cae782ff1a99fb95868043557f22c31b30c94afb9cf73278584e220a2ab6", size = 27126649, upload-time = "2025-08-26T07:42:37.696Z" }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/21/41960c03721a99e7be99a96ebb8570bdfd6f76f512b5d09074365e27ce28/pyside6_essentials-6.9.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:713eb8dcbb016ff10e6fca129c1bf2a0fd8cfac979e689264e0be3b332f9398e", size = 133092348, upload-time = "2025-08-26T07:43:57.231Z" }, + { url = "https://files.pythonhosted.org/packages/3e/02/e38ff18f3d2d8d3071aa6823031aad6089267aa4668181db65ce9948bfc0/pyside6_essentials-6.9.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:84b8ca4fa56506e2848bdb4c7a0851a5e7adcb916bef9bce25ce2eeb6c7002cc", size = 96569791, upload-time = "2025-08-26T07:44:41.392Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a1/1203d4db6919b42a937d9ac5ddb84b20ea42eb119f7c1ddeb77cb8fdb00c/pyside6_essentials-6.9.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:d0f701503974bd51b408966539aa6956f3d8536e547ea8002fbfb3d77796bbc3", size = 94311809, upload-time = "2025-08-26T07:46:44.924Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e3/3b3e869d3e332b6db93f6f64fac3b12f5c48b84f03f2aa50ee5c044ec0de/pyside6_essentials-6.9.2-cp39-abi3-win_amd64.whl", hash = "sha256:b2f746f795138ac63eb173f9850a6db293461a1b6ce22cf6dafac7d194a38951", size = 72624566, upload-time = "2025-08-26T07:48:04.64Z" }, + { url = "https://files.pythonhosted.org/packages/91/70/db78afc8b60b2e53f99145bde2f644cca43924a4dd869ffe664e0792730a/pyside6_essentials-6.9.2-cp39-abi3-win_arm64.whl", hash = "sha256:ecd7b5cd9e271f397fb89a6357f4ec301d8163e50869c6c557f9ccc6bed42789", size = 49561720, upload-time = "2025-08-26T07:49:43.708Z" }, +] + +[[package]] +name = "python-xlib" +version = "0.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shiboken6" +version = "6.9.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/1e/62a8757aa0aa8d5dbf876f6cb6f652a60be9852e7911b59269dd983a7fb5/shiboken6-6.9.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:8bb1c4326330e53adeac98bfd9dcf57f5173a50318a180938dcc4825d9ca38da", size = 406337, upload-time = "2025-08-26T07:52:39.614Z" }, + { url = "https://files.pythonhosted.org/packages/3b/bb/72a8ed0f0542d9ea935f385b396ee6a4bbd94749c817cbf2be34e80a16d3/shiboken6-6.9.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3b54c0a12ea1b03b9dc5dcfb603c366e957dc75341bf7cb1cc436d0d848308ee", size = 206733, upload-time = "2025-08-26T07:52:41.768Z" }, + { url = "https://files.pythonhosted.org/packages/52/c4/09e902f5612a509cef2c8712c516e4fe44f3a1ae9fcd8921baddb5e6bae4/shiboken6-6.9.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a5f5985938f5acb604c23536a0ff2efb3cccb77d23da91fbaff8fd8ded3dceb4", size = 202784, upload-time = "2025-08-26T07:52:43.172Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ea/a56b094a4bf6facf89f52f58e83684e168b1be08c14feb8b99969f3d4189/shiboken6-6.9.2-cp39-abi3-win_amd64.whl", hash = "sha256:68c33d565cd4732be762d19ff67dfc53763256bac413d392aa8598b524980bc4", size = 1152089, upload-time = "2025-08-26T07:52:45.162Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/562a527fc55fbf41fa70dae735929988215505cb5ec0809fb0aef921d4a0/shiboken6-6.9.2-cp39-abi3-win_arm64.whl", hash = "sha256:c5b827797b3d89d9b9a3753371ff533fcd4afc4531ca51a7c696952132098054", size = 1708948, upload-time = "2025-08-26T07:52:48.016Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "windows-and-linux" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "darkdetect" }, + { name = "dbus-next" }, + { name = "google-generativeai" }, + { name = "markdown2" }, + { name = "ollama" }, + { name = "openai" }, + { name = "pyinstaller" }, + { name = "pynput" }, + { name = "pyperclip" }, + { name = "pyside6" }, +] + +[package.metadata] +requires-dist = [ + { name = "darkdetect", specifier = ">=0.8.0" }, + { name = "dbus-next", specifier = ">=0.2.3" }, + { name = "google-generativeai", specifier = ">=0.8.5" }, + { name = "markdown2", specifier = ">=2.5.4" }, + { name = "ollama", specifier = ">=0.6.0" }, + { name = "openai", specifier = ">=1.109.1" }, + { name = "pyinstaller", specifier = ">=6.16.0" }, + { name = "pynput", specifier = ">=1.8.1" }, + { name = "pyperclip", specifier = ">=1.11.0" }, + { name = "pyside6", specifier = ">=6.9.2" }, +]