Repository: sharingan-no-kakashi/orange-intelligence Branch: main Commit: 02e7d3ff8e80 Files: 29 Total size: 45.9 KB Directory structure: gitextract_roayer6_/ ├── .flake8 ├── .gitignore ├── Contributing.md ├── README.md ├── app.py ├── config.py ├── core/ │ ├── __init__.py │ ├── controller.py │ ├── model.py │ └── views/ │ ├── __init__.py │ ├── floating_window.py │ ├── styling/ │ │ ├── __init__.py │ │ └── floating_window_style.py │ ├── system_tray.py │ └── text_processing.py ├── extensions/ │ ├── __init__.py │ ├── basics/ │ │ └── __init__.py │ ├── langraph/ │ │ ├── __init__.py │ │ └── langraph.py │ ├── ollama/ │ │ ├── __init__.py │ │ └── example.py │ ├── openai/ │ │ ├── __init__.py │ │ └── utils.py │ └── variables.py ├── makefile ├── pyproject.toml ├── tests/ │ ├── __init__.py │ └── test_dummy.py └── utils/ └── __init__.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .flake8 ================================================ [flake8] max-line-length = 120 exclude = .git, __pycache__, .venv, build, dist, # ANN101: Missing type annotation for self in method # ANN102: Missing type annotation for cls in method # D400: First line should end with a period for a docstring # D401: First line should be in imperative mood for a docstring # ANN003: Missing type annotation for a kwargs argument extend-ignore=ANN101,ANN102,D400,D401,ANN003,N802 suppress-none-returning=true ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # UV # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. #uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ # Ruff stuff: .ruff_cache/ # PyPI configuration file .pypirc .vscode/ .python-version ================================================ FILE: Contributing.md ================================================ # Contributing to Orange Intelligence We’re thrilled that you want to contribute to Orange Intelligence! While we don’t have a fully established contribution process at the moment, here’s what it should look like: --- ## 🛠 Contribution Workflow 1. **Open an Issue** - Start by opening an issue in this repository. - Use a proper tag to classify your issue: - `ux`: For UI/UX improvements. - `bug`: For bugs or unexpected behavior. - `feature-request`: For new features you'd like to see. - `extension-function`: For proposing new functions/extensions to be added to the floating window. - Wait for feedback before diving into implementation. We don’t want you to waste valuable time on something that might not align with the project’s goals. 2. **Create a Pull Request (PR)** - Once your issue is approved, fork the repository and start working on your changes. - When ready, submit a PR linked to your issue. 3. **Make the CI Happy** - Ensure your code passes all linting and tests. Run these locally before pushing your changes. 4. **Celebrate Your Contribution!** - Once your PR is merged, enjoy the satisfaction of contributing to an open-source project. 🎉 --- ## 🧹 Code Style We strive to maintain clean, consistent, and readable code. Please follow these practices: - Run linters (`black`, `flake8`) before submitting your PR. - Include unit tests for new features or fixes where applicable. --- ## 📝 Notes - If you have ideas but aren’t sure how to implement them, feel free to open an issue just to discuss! - Don’t hesitate to ask questions if you’re stuck—collaboration is key. --- Thank you for helping make Orange Intelligence better! 🧡 ================================================ FILE: README.md ================================================ # 🍊 Orange Intelligence **A Better Open Source Version of Apple Intelligence for macOS** Orange Intelligence is a powerful, fully customizable productivity tool for macOS. With its elegant floating window interface, you can capture, process, and replace text seamlessly across any application. Whether you're running basic text processing, leveraging the power of large language models (LLMs) like OpenAI or local LLaMA, or creating complex agent systems, Orange Intelligence empowers you to work smarter, faster, and better. --- ## 🌟 Why Orange Intelligence? Apple Intelligence is closed, limited, and inflexible. Orange Intelligence brings the power of customization and open source innovation to macOS, making it the perfect productivity tool for developers, researchers, and AI enthusiasts. --- ## Demo | Feature | Demo GIF | |----------------------------------|-------------------------------| | **Variables and text processing examples** | ![alt text](example1.gif) | | **LLMs with local Ollama** | ![alt text](example2.gif) | ## ✨ Features - **Floating Text Processor**: Double-tap the `Option` key to open a sleek floating window. - **Run Any Python Function**: Execute any Python code, from simple string manipulations to advanced AI/LLM integrations (OpenAI, local LLaMA, or your own agents). - **Fully Customizable**: Add your own Python logic to extend its functionality and tailor the app to your specific workflows. - **Global variable replacement**: Global variable replacement (You no longer need to open your notes app to do a simple copy and paste!) --- ## 🛠️ How It Works Orange Intelligence simplifies text processing with a three-step pipeline: 1. **Capture Text from Any Application** - Using a clever Applescript trick, the app simulates a global `Cmd+C` to grab the content of the clipboard from the active application. 2. **Process the Text** - The floating window opens in focus, allowing the user to select which Python function to run. The selected function processes the clipboard text (e.g., formatting, AI generation, etc.). 3. **Replace the Text** - Once processed, the floating window closes, the focus returns to the previous app, and a global `Cmd+V` pastes the updated content back into the app. --- ## 🚀 Getting Started ### Prerequisites 1. **Install Python 3.9+** 2. **Install Poetry** for dependency management: ```bash pip install poetry ``` 3. **Grant Permissions**: Ensure your Python interpreter has the following rights: - **Accessibility**: To allow replacing text via `Cmd+V`. - **Input Monitoring**: To listen for global key shortcuts like the `Option` key. 4. **Optional Dependencies**: - **Ollama**: If you want to run local LLaMA models, ensure [Ollama](https://ollama.ai/) is installed and running. - If you have a custom setup, you might need to adapt the Python code for your environment. 5. **Optional Configuration**: - Adjust the log level in `config.py` if needed. ### Installation 1. Clone this repository: ```bash git clone [https://github.com/sharingan-no-kakashi/orange-intelligence.git] cd orange-intelligence ``` 2. Install dependencies using Poetry: ```bash poetry install ``` 3. Start the app using Make: ```bash make run ``` (Use `make help` to see other available commands.) 4. When the **orange icon** appears in the system tray, the app is ready to use. ### Usage 1. Highlight any text in a macOS application. 2. Double-tap the `Option` key to bring up the floating window. 3. Select a function to apply to the highlighted text. --- ## 🧩 Customization - Add custom logic by defining callable Python objects in the `extensions` package. - **Every function (or callable object)** defined in the `__init__.py` file of the `extensions` package will automatically appear as an option in the floating window. Example: ```python # extensions/__init__.py def reverse_text(input_text): return input_text[::-1] ``` --- ## 📝 To-Do and Future Improvements These are the features currently in progress: - **Custom Prompts**: Add the ability to pass custom prompts (hint: that's why you see `**kwargs` in the code). - **Text Playground**: Open a new custom window for text processing, allowing users to combine LLM/Python/text utilities in a single workspace. - **Clipboard Restoration**: Automatically restore the content of the clipboard after processing operations. - **UI/UX Enhancements**: Improve the design and user experience for better usability. - **Code improvements**: The codebase feels a bit hacky at the moment, there are lots debatable decisions (subprocessing/time.sleep(), controller feels a bit overwhelmed of logic) etc - **Other platforms support** There is probably no reason why this cannot be extended to other platforms linux/window. - **CI/CD** Setup a nice ci pipeline to run the lint/banding/mypy and handling versions/releases --- ## 🏗️ Tech Stack - **Python**: The core logic of the app is written in Python for flexibility and extensibility. - **PyQt6**: Provides a polished and responsive UI for the floating window, built with a clean MVC architecture. - **Applescript**: Facilitates system-level clipboard interactions and application switching. --- ## 📝 License This project is licensed under the [MIT License](LICENSE). --- ## 🤝 Contributing We welcome contributions! Check out the [CONTRIBUTING.md](Contributing.md) for guidelines on how to get involved. --- ## 📧 Contact Questions, feedback, or ideas? Reach out via Github issues. --- **Empower your workflow with 🍊 Orange Intelligence — because better is open source.** ================================================ FILE: app.py ================================================ import logging.config import sys from config import CONFIG from PyQt6.QtWidgets import QApplication from utils import avoid_dock_macos_icon from core.controller import Controller from core.model import Model def main(): logging.config.dictConfig(CONFIG["logging"]) app = QApplication(sys.argv) avoid_dock_macos_icon() model = Model() controller = Controller(model=model, view=app) # Run the event loop sys.exit(controller.view.exec()) if __name__ == "__main__": main() ================================================ FILE: config.py ================================================ import os import sys import tempfile LOGGING_LEVEL = "DEBUG" CONFIG = { "app": { "name": "orange-ai", "icon": "assets/icon.png", }, "variables": { "import_bash_profile": False, }, "ollama": { "name": "ollama", "url": "https://ollama.com", "default_model": "llama3.1", }, "openai": {"api_key": os.environ.get("OPENAPI_KEY"), "default_model": "gpt-3.5-turbo"}, "logging": { "version": 1, "disable_existing_loggers": False, "formatters": { "detailed": {"format": "%(asctime)s - %(levelname)s - %(name)s - %(message)s"}, }, "handlers": { "stream": { "level": LOGGING_LEVEL, "class": "logging.StreamHandler", "stream": sys.stdout, "formatter": "detailed", }, "file": { "level": LOGGING_LEVEL, "class": "logging.FileHandler", "filename": tempfile.NamedTemporaryFile(delete=False).name, "formatter": "detailed", }, }, "loggers": { "": { # Root logger configuration "handlers": ["stream", "file"], "level": LOGGING_LEVEL, "propagate": True, }, }, }, } ================================================ FILE: core/__init__.py ================================================ ================================================ FILE: core/controller.py ================================================ import logging import time import pyperclip from pynput import keyboard from PyQt6.QtCore import QTimer, pyqtSignal from PyQt6.QtWidgets import QApplication from utils import cmd_v, get_current_process_id, get_focused_text, put_app_in_focus, return_app_in_focus from core.model import Model from core.views.floating_window import FloatingWindow from core.views.system_tray import SystemTray from core.views.text_processing import TextWindow LOG = logging.getLogger(__name__) class Controller: def __init__(self, model: Model, view: QApplication): self.view = view self.model = model self.option_key = False self.last_time = 0.0 self.floating_window_open = False self.text_window_open = False self.focused_process_id = "" self.focused_text = "" self.processed_text = "" self.cmd_pressed = False self.option_pressed = False self.this_process_id = get_current_process_id() self.setup_windows() self.listener = keyboard.Listener(on_press=self.on_press, on_release=self.on_release) self.listener.start() def get_window_tabs_items(self) -> dict[str, list[str]]: return self.model.sections def update_floating_window_status(self, statue: bool): self.floating_window_open = statue def update_text_window_status(self, status: bool): self.text_window_open = status def setup_event_handlers(self): self.view.floating_window.custom_signal.connect(self.process_text) self.view.floating_window.event_put_app_focus.connect(self.put_this_app_in_focus) self.view.text_window.event_put_app_focus.connect(self.put_this_app_in_focus) self.view.floating_window.windows_event.connect(self.update_floating_window_status) self.view.text_window.windows_event.connect(self.update_text_window_status) def recreate_text_window(self): LOG.debug("Recreating text window") self.view.text_window = TextWindow( processing_text=self.focused_text, functions_list=self.model.get_all_functions_flattened() ) LOG.debug("Text window recreated") def setup_windows(self) -> None: self.view.main_window = SystemTray() # Create the system tray icon self.view.main_window.show() # Create the floating window, passing the key listener to it tabs = self.get_window_tabs_items() self.view.floating_window = FloatingWindow(tabs) self.recreate_text_window() self.setup_event_handlers() def get_focused_text(self) -> None: self.focused_text = get_focused_text() def return_app_in_focus(self) -> None: self.focused_process_id = return_app_in_focus() def put_previous_app_in_focus(self) -> None: return put_app_in_focus(self.focused_process_id) def put_this_app_in_focus(self) -> None: return put_app_in_focus(self.this_process_id) def set_processed_text(self, section: str, item: str, **kwargs) -> str: processed_text = self.model.process_text(section, item, self.focused_text, **{}) pyperclip.copy(processed_text) self.put_previous_app_in_focus() time.sleep(0.3) cmd_v() return self.processed_text def process_text(self, section: str, item: str) -> None: QTimer.singleShot(0, lambda: self.set_processed_text(section, item)) def on_press(self, key: keyboard.Key) -> None: try: # Detect if the pressed key is Cmd or Option (Alt) if key == keyboard.Key.alt_l or key == keyboard.Key.alt_r: self.option_pressed = True elif key == keyboard.Key.cmd_l or key == keyboard.Key.cmd_r: self.cmd_pressed = True # If both Cmd and Option keys are pressed simultaneously, open another window if self.cmd_pressed and self.option_pressed: if not self.text_window_open: self.open_text_window() self.text_window_open = True self.cmd_pressed = False # Reset after action to avoid repeated detection self.option_pressed = False # If only the Option key is pressed, follow the original logic elif self.option_pressed: current_time = time.time() # If two presses are detected within 1 second, open the window if current_time - self.last_time < 0.8: if self.floating_window_open: self.close_floating_window() else: self.open_floating_window() self.last_time = current_time self.option_pressed = False # Reset after action except AttributeError as e: LOG.debug(f"AttributeError: {e}") pass def _open_text_window(self) -> None: LOG.debug("Opening text window") self.recreate_text_window() def open_text_window(self) -> None: pass # This is still unreliable def open_floating_window(self) -> None: self.get_focused_text() self.return_app_in_focus() QTimer.singleShot(0, self.view.floating_window.show) self.floating_window_open = True def close_floating_window(self) -> None: QTimer.singleShot(0, self.view.floating_window.close) self.put_previous_app_in_focus() self.floating_window_open = False def on_release(self, key: keyboard.Key) -> None: # Reset flags when keys are released if key == keyboard.Key.alt_l or key == keyboard.Key.alt_r: self.option_pressed = False elif key == keyboard.Key.cmd_l or key == keyboard.Key.cmd_r: self.cmd_pressed = False ================================================ FILE: core/model.py ================================================ import logging from utils import load_all_available_functions import extensions import extensions.ollama.example from extensions.variables import variables LOG = logging.getLogger(__name__) from functools import reduce class Model: def __init__(self): self.functions = load_all_available_functions(extensions) self.sections = self.get_sections() self.variables = variables def get_all_functions_flattened(self) -> dict[str, str]: return reduce(lambda x, y: {**x, **y}, self.functions.values(), {}) def get_sections(self) -> dict[str, list[str]]: sections = {section: list(functions.keys()) for section, functions in self.functions.items()} sections["variables"] = list(variables.keys()) return sections def process_text(self, section: str, function_name: str, input_text: str, **kwargs) -> str: if section == "variables": return self.variables[function_name] return self.functions[section][function_name](input_text, **kwargs) ================================================ FILE: core/views/__init__.py ================================================ ================================================ FILE: core/views/floating_window.py ================================================ import logging from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QCloseEvent, QHideEvent, QKeyEvent, QShowEvent from PyQt6.QtWidgets import QListWidget, QListWidgetItem, QTabWidget, QVBoxLayout, QWidget from core.views.styling.floating_window_style import FloatingWindowStyleOptions LOG = logging.getLogger(__name__) class FloatingWindow(QWidget): custom_signal = pyqtSignal(str, str) windows_event = pyqtSignal(bool) process_text_event = pyqtSignal(str, str, str) event_put_app_focus = pyqtSignal() def __init__(self, tab_sections: dict): super().__init__() # Set macOS-style window appearance self.setWindowTitle(FloatingWindowStyleOptions.title) self.setGeometry(200, 200, 400, 300) self.setStyleSheet(FloatingWindowStyleOptions.base) self.tab_widget = QTabWidget() self.tab_scroll_positions = {} self.set_up_tab_widget(tab_sections) def keyPressEvent(self, event: QKeyEvent) -> None: current_index = self.tab_widget.currentIndex() current_widget = self.tab_widget.currentWidget() if event.key() == Qt.Key.Key_Escape: self.close() # Retrieve the QListWidget inside the current tab if current_widget: list_widget = current_widget.findChild(QListWidget) if list_widget and isinstance(list_widget, QListWidget): # If the current tab contains a QListWidget if event.key() == Qt.Key.Key_Up: current_row = list_widget.currentRow() new_row = max(0, current_row - 1) list_widget.setCurrentRow(new_row) elif event.key() == Qt.Key.Key_Down: current_row = list_widget.currentRow() new_row = min(list_widget.count() - 1, current_row + 1) list_widget.setCurrentRow(new_row) if event.key() == Qt.Key.Key_Return: # Handle Enter key current_item = list_widget.currentItem() # Get the currently selected item if current_item: tab_name = self.tab_widget.tabText(current_index) row_text = current_item.text() self.handle_enter_key(tab_name, row_text, current_index) else: LOG.debug("No item selected in the current list.") else: LOG.debug("Current tab does not contain a QListWidget.") else: LOG.debug("No current widget in the tab.") # Handle Left and Right arrow keys to switch tabs if event.key() == Qt.Key.Key_Right: next_index = (current_index + 1) % self.tab_widget.count() self.tab_widget.setCurrentIndex(next_index) elif event.key() == Qt.Key.Key_Left: previous_index = (current_index - 1) % self.tab_widget.count() self.tab_widget.setCurrentIndex(previous_index) else: # Pass other key events to the parent class super().keyPressEvent(event) def set_up_tab_widget(self, tab_sections: dict) -> None: # Create the tab widget self.tab_widget.setTabsClosable(False) self.tab_widget.setStyleSheet(FloatingWindowStyleOptions.tab_widget) # Create tabs self.create_tabs(tab_sections) # Main layout main_layout = QVBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(self.tab_widget) self.setLayout(main_layout) # Initialize scroll positions for all tabs for i in range(self.tab_widget.count()): self.tab_scroll_positions[i] = 0 def create_tabs(self, tab_sections: dict) -> None: for section_name, tab_section in tab_sections.items(): tab = QWidget() tab_layout = QVBoxLayout() list_widget = QListWidget() list_widget.setStyleSheet(FloatingWindowStyleOptions.list_widget) tab_layout.addWidget(list_widget) for key in tab_section: item = QListWidgetItem(key) list_widget.addItem(item) list_widget.setCurrentRow(0) # Set the current row to the first item tab.setLayout(tab_layout) # Set layout for the tab self.tab_widget.addTab(tab, section_name) # Add the tab def handle_enter_key(self, tab_name: str, text_item: str, tab_index: int) -> None: """Close the window and trigger the processText function.""" self.custom_signal.emit(tab_name, text_item) self.close() # Close the window def closeEvent(self, event: QCloseEvent) -> None: # Hide the window instead of closing it to prevent application shutdown event.ignore() self.hide() # Update the key listener state when the window is hidden self.windows_event.emit(False) def hideEvent(self, event: QHideEvent) -> None: # Ensure the key listener flag is updated when the window is hidden self.windows_event.emit(False) def showEvent(self, event: QShowEvent) -> None: super().showEvent(event) # Ensure the window is focused when shown self.raise_() # Raise the window to the top self.activateWindow() # Make the window active (focused) self.windows_event.emit(True) self.event_put_app_focus.emit() ================================================ FILE: core/views/styling/__init__.py ================================================ ================================================ FILE: core/views/styling/floating_window_style.py ================================================ class FloatingWindowStyleOptions: geometry = "200, 200, 400, 300" title = "Orange GUI" base = """ QWidget { background-color: #f4f4f4; border: 1px solid #ccc; border-radius: 10px; padding: 0; } """ tab_widget = """ QTabWidget::pane { border: none; background: transparent; } QTabBar::tab { background: #f8f8f8; border: none; padding: 8px 16px; margin: 2px; border-top-left-radius: 8px; border-top-right-radius: 8px; } QTabBar::tab:selected { background: #ffffff; font-weight: bold; color: #007aff; } QTabBar::tab:hover { background: #e9e9e9; } """ search_bar = """ QLineEdit { background-color: #ffffff; border: 1px solid #d1d1d1; border-radius: 6px; padding: 6px; margin: 6px 0; } """ list_widget = """ QListWidget { background-color: #ffffff; border: 1px solid #d1d1d1; border-radius: 6px; margin: 8px; } QListWidget::item { padding: 8px; font-size: 14px; color: #333; } QListWidget::item:selected { background-color: #007aff; color: #ffffff; font-weight: bold; border-radius: 4px; margin: 2px; } QListWidget::item:hover { background-color: #e0e0e0; color: #000; } """ ================================================ FILE: core/views/system_tray.py ================================================ from config import CONFIG from PyQt6.QtCore import QCoreApplication from PyQt6.QtGui import QAction, QIcon from PyQt6.QtWidgets import QMenu, QSystemTrayIcon class SystemTray(QSystemTrayIcon): def __init__(self): icon = CONFIG.get("app").get("icon") super().__init__(QIcon(icon)) # Create the tray menu self.menu = QMenu() # Add actions to the menu self.create_menu_actions() # Set the menu to the tray icon self.setContextMenu(self.menu) def create_menu_actions(self): """Creates and adds actions to the context menu.""" # Action to quit the application quit_action = QAction("❌ Quit", self) quit_action.triggered.connect(self.quit_app) quit_action.setToolTip("Quit the application") # Add actions to the menu self.menu.addAction(quit_action) def open_settings(self): """Placeholder for a settings dialog.""" self.show_message("Settings", "Settings window would appear here.") def show_message(self, title: str, message: str): """Displays a message balloon from the system tray.""" self.showMessage(title, message, QSystemTrayIcon.MessageIcon.Information) def quit_app(self): """Exits the application.""" QCoreApplication.quit() ================================================ FILE: core/views/text_processing.py ================================================ import logging from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QCloseEvent, QHideEvent, QShowEvent from PyQt6.QtWidgets import ( QAbstractItemView, QFrame, QHBoxLayout, QLineEdit, QListWidget, QPushButton, QSplitter, QTabWidget, QTextEdit, QVBoxLayout, QWidget, ) LOG = logging.getLogger(__name__) class TextWindow(QWidget): custom_signal = pyqtSignal(str, str) windows_event = pyqtSignal(bool) process_text_event = pyqtSignal(str, str, str) event_put_app_focus = pyqtSignal() def __init__(self, processing_text, functions_list): super().__init__() LOG.debug(f"Creating text window processing text, {processing_text}") # Layout for the main window main_layout = QHBoxLayout(self) self.processing_text = processing_text # Create QTabWidget for text areas self.text_tab_widget = QTabWidget() self.functions_list = functions_list # Add initial text tab self.text_tab = QWidget() text_layout = QVBoxLayout(self.text_tab) self.text_widget = QTextEdit() self.text_widget.setText(self.processing_text) text_layout.addWidget(self.text_widget) self.text_tab_widget.addTab(self.text_tab, "Text 1") # Create QTabWidget for functions list self.function_tab_widget = QTabWidget() # Tab 1 - Function list self.function_list_tab = QWidget() function_layout = QVBoxLayout(self.function_list_tab) self.function_list_widget = QListWidget() function_layout.addWidget(self.function_list_widget) self.function_tab_widget.addTab(self.function_list_tab, "Functions 1") # Add function names to the list for function_name in functions_list: self.function_list_widget.addItem(function_name) # Set the function list widget to be selectable self.function_list_widget.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) # Tab 2 - Text area for functions self.function_text_tab = QWidget() function_text_layout = QVBoxLayout(self.function_text_tab) function_text_widget = QTextEdit() function_text_layout.addWidget(function_text_widget) self.function_tab_widget.addTab(self.function_text_tab, "Function Text") # Split the main layout into two sections: one for text and one for functions splitter = QSplitter(Qt.Orientation.Horizontal) splitter.addWidget(self.text_tab_widget) splitter.addWidget(self.function_tab_widget) # Set stretch factors to ensure text and function sections take 90% of the space splitter.setStretchFactor(0, 9) # Text section takes 9 parts splitter.setStretchFactor(1, 9) # Function section takes 9 parts # Add splitter to the main layout main_layout.addWidget(splitter) # Bottom input area with 3 text inputs and buttons, occupying 10% of the horizontal space bottom_layout = QVBoxLayout() # Create a container frame for the bottom section to control its width bottom_frame = QFrame() bottom_frame.setLayout(bottom_layout) # Create 3 text input fields and 3 run buttons, and add them vertically self.inputs = [QLineEdit() for _ in range(3)] self.run_buttons = [QPushButton("Run") for _ in range(3)] for input_field, button in zip(self.inputs, self.run_buttons): bottom_layout.addWidget(input_field) bottom_layout.addWidget(button) # Add the bottom frame to the main layout, occupying 10% of the horizontal space bottom_frame.setFixedWidth(self.width() // 10) # Set the width to 10% of the window size main_layout.addWidget(bottom_frame) self.setLayout(main_layout) self.setWindowTitle("Text Display and Function List") self.resize(800, 600) # Connect context menus for adding new tabs self.text_tab_widget.tabBar().setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.text_tab_widget.tabBar().customContextMenuRequested.connect(self.showTextTabMenu) self.function_tab_widget.tabBar().setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.function_tab_widget.tabBar().customContextMenuRequested.connect(self.showFunctionTabMenu) # Connect the Enter key event to the function application self.function_list_widget.keyPressEvent = self.handle_function_key_event def handle_function_key_event(self, event): if event.key() == Qt.Key.Key_Return: # Detect Enter key press # Get the selected function name from the list selected_item = self.function_list_widget.currentItem() if selected_item: function_name = selected_item.text() # Apply the function to the displayed text self.apply_function(function_name) else: super().keyPressEvent(event) # Pass the event to the base class if it's not Enter def apply_function(self, function_name): # Check if the function name exists in the functions list if function_name in self.functions_list: # Apply the function to the current text in the text widget func = self.functions_list[function_name] new_text = func(self.text_widget.toPlainText()) self.text_widget.setText(new_text) # Update the text widget with the result def showTextTabMenu(self, pos): # Create and show context menu for adding new text tabs menu = self.text_tab_widget.tabBar().createStandardContextMenu() add_tab_action = menu.addAction("Add New Text Tab") add_tab_action.triggered.connect(self.add_new_text_tab) menu.exec(self.text_tab_widget.tabBar().mapToGlobal(pos)) def showFunctionTabMenu(self, pos): # Create and show context menu for adding new function tabs menu = self.function_tab_widget.tabBar().createStandardContextMenu() add_tab_action = menu.addAction("Add New Function Tab") add_tab_action.triggered.connect(self.add_new_function_tab) menu.exec(self.function_tab_widget.tabBar().mapToGlobal(pos)) def add_new_text_tab(self): # Create new text tab new_text_tab = QWidget() new_text_layout = QVBoxLayout(new_text_tab) new_text_widget = QTextEdit() new_text_layout.addWidget(new_text_widget) # Add the new text tab new_tab_index = self.text_tab_widget.addTab(new_text_tab, f"Text {self.text_tab_widget.count() + 1}") # Optionally set the new tab as the current tab self.text_tab_widget.setCurrentIndex(new_tab_index) def add_new_function_tab(self): # Create new function list tab new_function_list_tab = QWidget() new_function_list_layout = QVBoxLayout(new_function_list_tab) new_function_list_widget = QListWidget() new_function_list_widget.addItem("Uppercase") # Default function item new_function_list_widget.addItem("Lowercase") new_function_list_layout.addWidget(new_function_list_widget) # Add the new function list tab new_function_list_tab_index = self.function_tab_widget.addTab( new_function_list_tab, f"Functions {self.function_tab_widget.count() + 1}" ) # Create new function text tab new_function_text_tab = QWidget() new_function_text_layout = QVBoxLayout(new_function_text_tab) new_function_text_widget = QTextEdit() new_function_text_layout.addWidget(new_function_text_widget) # Add the new function text tab new_function_text_tab_index = self.function_tab_widget.addTab( new_function_text_tab, f"Function Text {self.function_tab_widget.count()}" ) # Optionally set the new function list tab as the current tab self.function_tab_widget.setCurrentIndex(new_function_list_tab_index) def closeEvent(self, event: QCloseEvent) -> None: # Hide the window instead of closing it to prevent application shutdown event.ignore() self.hide() # Update the key listener state when the window is hidden self.windows_event.emit(False) def hideEvent(self, event: QHideEvent) -> None: # Ensure the key listener flag is updated when the window is hidden self.windows_event.emit(False) def showEvent(self, event: QShowEvent) -> None: LOG.debug("Text window shown") super().showEvent(event) # Ensure the window is focused when shown self.raise_() # Raise the window to the top self.activateWindow() # Make the window active (focused) self.windows_event.emit(True) self.event_put_app_focus.emit() ================================================ FILE: extensions/__init__.py ================================================ ================================================ FILE: extensions/basics/__init__.py ================================================ import json def upper_case(text: str, **kwargs) -> str: return text.upper() def lower_case(text: str, **kwargs) -> str: return text.lower() def pretty_json(text: str, **kwargs) -> str: return json.dumps(json.loads(text), indent=4) def a_complex_task_you_do_not_want_to_implement_now(text: str, **kwargs) -> str: return "This is a complex task that you do not want to implement now." ================================================ FILE: extensions/langraph/__init__.py ================================================ ================================================ FILE: extensions/langraph/langraph.py ================================================ ================================================ FILE: extensions/ollama/__init__.py ================================================ from extensions.ollama.example import improve_grammar, make_it_polite, translate_to_english # noqa: F401 ================================================ FILE: extensions/ollama/example.py ================================================ import logging from config import CONFIG from ollama import ChatResponse, chat LOG = logging.getLogger(__name__) def ollama(prompt: str) -> str: response: ChatResponse = chat( model=CONFIG["ollama"]["default_model"], messages=[ { "role": "user", "content": prompt, }, ], ) # or access fields directly from the response object LOG.debug(response.message.content) return response.message.content def improve_grammar(input_text: str, **kwargs) -> str: prompt = ( "Improve the grammar of this sentence. Return only the improved sentence, do not add anything else. " + input_text ) return ollama(prompt) def translate_to_english(input_text: str, **kwargs) -> str: prompt = ( "Translate this sentence to English. Return only the translated sentence, do not add anything else. " + input_text ) return ollama(prompt) def make_it_polite(input_text: str, **kwargs) -> str: prompt = ( "Convert this sentence to a polite one. Return only the converted sentence, do not add anything else. " + input_text ) return ollama(prompt) ================================================ FILE: extensions/openai/__init__.py ================================================ from extensions.openai.utils import _chat_completion_endpoint def make_a_joke(text: str, **kwargs) -> str: prompt = f"Tell me a joke about {text}" return _chat_completion_endpoint(prompt) ================================================ FILE: extensions/openai/utils.py ================================================ from config import CONFIG from openai import OpenAI def _chat_completion_endpoint(content: str) -> str: client = OpenAI(api_key=CONFIG["openai"]["api_key"]) chat_completion = client.chat.completions.create( messages=[ { "role": "user", "content": content, } ], model=CONFIG["openai"]["default_model"], ) return chat_completion.choices[0].message.content ================================================ FILE: extensions/variables.py ================================================ import os dev_db_user = os.environ.get("dev_db_user") or "user" variables = { "my name:": "Yoda", "thing i always forget": "42", "s3 bucket with a weird name": "prod-thing-i-always-forget", "Prd kafka endpoint": "https://prod-thing-i-always-forget.s3.amazonaws.com", "that thing once told me": "John Snow knows nothing", "Json config dev": '{"s3bucket":"dev-bucket", "db": {"user": "%s"} }' % dev_db_user, } ================================================ FILE: makefile ================================================ PROJECT ?= orange-intelligence VERSION ?= $(shell grep -m 1 version pyproject.toml | tr -s ' ' | tr -d '"' | tr -d "'" | cut -d' ' -f3) PYTHON_VERSION = $(shell grep 'python =' pyproject.toml | sed -n 's/^python = ["^]*\([0-9]*\.[0-9]*\)\(.*\)"/\1/p') version: ## Show the current version echo "Current version: $(VERSION)" help: ## Show help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort |\ awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}' .DEFAULT_GOAL=help .python-version: ## Installs the correct version of python if required $(if $(shell pyenv versions --bare | grep "^$(PYTHON_VERSION)"),, pyenv install $(PYTHON_VERSION)) pyenv local $(shell pyenv versions --bare | grep "^$(PYTHON_VERSION)" | tail -n 1) .venv: .python-version ## Activates and installs the poetry environment. poetry env use ~/.pyenv/versions/$(shell cat $<)/bin/python && \ poetry install lint: .venv ## Lint the project poetry run flake8 mypy: .venv ## Run mypy poetry run mypy test: .venv ## Test the project poetry run pytest fmt: .venv ## Run formatting tools for project poetry run black . $(if $(CI),--check ,) && poetry run isort . $(if $(CI),--check ,) bandit: .venv ## Run bandit poetry run bandit -c pyproject.toml -r . check: fmt lint mypy bandit test ; ## Run all checks run: poetry run python3 app.py clean: ## Clean the project rm -rf .venv rm -rf .python-version rm -rf dist/ rm -rf __pycache__ rm -rf .pytest_cache rm -rf .mypy_cache ================================================ FILE: pyproject.toml ================================================ [tool.poetry] name = "orange-intelligence" version = "0.1.0" description = "The Orange Intelligence for mac os x" authors = ["Pietro"] readme = "README.md" packages = [ { include = "core" }, { include = "extensions" }, ] [tool.poetry.dependencies] python = "^3.9" pynput = "1.7.7" pyperclip="1.9.0" PyQt6="6.8.0" ollama="0.4.7" pyobjc-framework-Cocoa="11.0" openai="1.60.1" [tool.poetry.dev-dependencies] black = "^23.7.0" flake8 = "^6.1.0" isort = "^5.12.0" mypy = "^1.8.0" pytest = "^7.4.0" pytest-cov = "^4.1.0" flake8-eradicate = "^1.5.0" flake8-pytest-style = "^1.7.2" pep8-naming = "^0.13.3" flake8-bugbear = "^24.2.6" flake8-annotations = "^3.0.1" flake8-simplify = "^0.21.0" flake8-print = "^5.0.0" bandit = {extras = ["toml"], version = "^1.7.7"} [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 target-version = ['py312'] include = '\.pyi?$' exclude = ''' ^/( ( \.eggs | .venv | \.git | build | dist | notebooks ) ) ''' [tool.isort] line_length = 120 profile = "black" src_paths = ["core", "tests", "extensions"] [tool.pytest.ini_options] minversion = "6.0" testpaths = ["tests"] python_files = ["tests.py", "test_*.py", "*_tests.py"] [tool.coverage.report] exclude_also = [ "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] [tool.mypy] files = ["core/**/*.py", "extensions/**/*.py", "tests/**/*.py", "utils/**/*.py"] follow_imports = "normal" strict_optional = "False" warn_redundant_casts = "True" warn_unused_ignores = "True" disallow_any_generics = "False" check_untyped_defs = "True" no_implicit_reexport = "True" no_implicit_optional = "True" disallow_untyped_defs = "False" ignore_missing_imports = "True" namespace_packages = "True" disallow_any_unimported = "True" exclude = [] [tool.bandit] exclude_dirs = ["tests", ".venv", "dist"] ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/test_dummy.py ================================================ def test_dummy_assert_true(): assert True ================================================ FILE: utils/__init__.py ================================================ import importlib import logging import os import pkgutil import subprocess import sys import time import types import typing import pyperclip from AppKit import NSApp from PyQt6.QtCore import QProcess LOG = logging.getLogger(__name__) def get_current_process_id() -> str: return f"{os.getpid()}" def get_focused_text() -> str: cmd_c() time.sleep(0.3) # Get clipboard content clipboard_content = pyperclip.paste() return clipboard_content.strip() def cmd_v() -> None: applescript = """ tell application "System Events" keystroke "v" using {command down} end tell """ subprocess.run(["osascript", "-e", applescript]) def return_app_in_focus() -> str: command = """/usr/bin/osascript -e 'tell application "System Events" set frontApp to first application process whose frontmost is true return unix id of frontApp end tell' """ res = subprocess.run(command, shell=True, stdin=sys.stdin, stdout=subprocess.PIPE, stderr=sys.stderr, text=True) return res.stdout.strip() def cmd_c() -> None: applescript = """ tell application "System Events" keystroke "c" using {command down} end tell """ subprocess.run(["osascript", "-e", applescript]) def put_app_in_focus(process_id: str) -> None: script = f"""tell application "System Events" set frontmost of (first process whose unix id is {process_id}) to true end tell""" QProcess.startDetached("/usr/bin/osascript", ["-e", script]) def put_this_app_in_focus() -> None: this_process_id = get_current_process_id() return put_app_in_focus(this_process_id) def import_package_init_functions(package: types.ModuleType) -> dict[str, dict[str, typing.Callable]]: # List all submodules (modules and subpackages) in the package submodules = [module.name for module in pkgutil.iter_modules(package.__path__)] callables = {} for submodule in submodules: # Dynamically import the __init__.py of the subpackage try: subpackage = importlib.import_module(f"{package.__name__}.{submodule}") if hasattr(subpackage, "__init__"): # Get all callables (functions and callable objects) in the __init__.py of the subpackage subpackage_callables = { item: getattr(subpackage, item) for item in dir(subpackage) if callable(getattr(subpackage, item)) if not item.startswith("_") } # Store callables with their names callables[submodule] = subpackage_callables except Exception as e: LOG.error(f"Error importing {submodule}: {e}") return callables def load_all_available_functions(package: types.ModuleType) -> dict[str, dict[str, typing.Callable]]: return {name: functions for name, functions in import_package_init_functions(package).items() if len(functions) > 0} def avoid_dock_macos_icon(): NSApp.setActivationPolicy_(1)