├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── dependabot.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── har_and_cookies └── blackbox.json ├── pyproject.toml ├── pyqt_openai ├── __init__.py ├── aboutDialog.py ├── chat_widget │ ├── center │ │ ├── aiChatUnit.py │ │ ├── chatBrowser.py │ │ ├── chatHome.py │ │ ├── chatUnit.py │ │ ├── chatWidget.py │ │ ├── commandCompleter.py │ │ ├── commandSuggestionWidget.py │ │ ├── findTextWidget.py │ │ ├── menuWidget.py │ │ ├── messageTextBrowser.py │ │ ├── prompt.py │ │ ├── realtimeApiWidget.py │ │ ├── responseInfoDialog.py │ │ ├── textEditPrompt.py │ │ ├── textEditPromptGroup.py │ │ ├── uploadedImageFileWidget.py │ │ └── userChatUnit.py │ ├── chatMainWidget.py │ ├── left_sidebar │ │ ├── chatNavWidget.py │ │ ├── exportDialog.py │ │ ├── importDialog.py │ │ └── selectChatImportTypeDialog.py │ ├── llamaIndexThread.py │ ├── prompt_gen_widget │ │ ├── importPromptManualDialog.py │ │ ├── promptCsvRightFormSampleDialog.py │ │ ├── promptEntryDirectInputDialog.py │ │ ├── promptGeneratorWidget.py │ │ ├── promptGroupDirectInputDialog.py │ │ ├── promptGroupExportDialog.py │ │ ├── promptGroupImportDialog.py │ │ ├── promptGroupList.py │ │ ├── promptPage.py │ │ └── promptTable.py │ └── right_sidebar │ │ ├── chatRightSideBarWidget.py │ │ ├── llama_widget │ │ ├── filesWidget.py │ │ ├── llamaPage.py │ │ └── supportedFileFormatsWidget.py │ │ ├── modelSearchBar.py │ │ ├── usingAPIPage.py │ │ └── usingG4FPage.py ├── config_loader.py ├── customizeDialog.py ├── dalle_widget │ ├── dalleHome.py │ ├── dalleMainWidget.py │ ├── dalleRightSideBar.py │ └── dalleThread.py ├── doNotAskAgainDialog.py ├── fontWidget.py ├── g4f_image_widget │ ├── g4fImageHome.py │ ├── g4fImageMainWidget.py │ ├── g4fImageRightSideBar.py │ └── g4fImageThread.py ├── globals.py ├── ico │ ├── add.svg │ ├── case.svg │ ├── close.svg │ ├── copy.svg │ ├── customize.svg │ ├── delete.svg │ ├── discord.svg │ ├── export.svg │ ├── favorite_no.svg │ ├── favorite_yes.svg │ ├── file.svg │ ├── focus_mode.svg │ ├── fullscreen.svg │ ├── github.svg │ ├── history.svg │ ├── import.svg │ ├── info.svg │ ├── kofi.png │ ├── next.svg │ ├── openai.png │ ├── openai.svg │ ├── patreon.svg │ ├── paypal.png │ ├── prev.svg │ ├── prompt.svg │ ├── question.svg │ ├── realtime_api.svg │ ├── record.svg │ ├── refresh.svg │ ├── regex.svg │ ├── save.svg │ ├── search.svg │ ├── send.svg │ ├── setting.svg │ ├── shortcut.svg │ ├── sidebar.svg │ ├── speaker.svg │ ├── stackontop.svg │ ├── update.svg │ ├── user.png │ ├── user.svg │ ├── vertical_three_dots.svg │ └── word.svg ├── icon.ico ├── img │ └── import_prompt_with_csv_right_form.png ├── lang │ ├── translations.json │ └── translations.py ├── main.py ├── mainWindow.py ├── models.py ├── prompt_res │ └── alex_brogan.json ├── replicate_widget │ ├── replicateHome.py │ ├── replicateMainWidget.py │ ├── replicateRightSideBar.py │ └── replicateThread.py ├── settings_dialog │ ├── apiWidget.py │ ├── generalSettingsWidget.py │ ├── markdownSettingsWidget.py │ ├── settingsDialog.py │ └── voiceSettingsWidget.py ├── shortcutDialog.py ├── sqlite.py ├── updateSoftwareDialog.py ├── util │ ├── button_style_helper.py │ ├── common.py │ ├── llamaindex.py │ └── replicate.py └── widgets │ ├── APIInputButton.py │ ├── animationButton.py │ ├── baseNavWidget.py │ ├── button.py │ ├── checkBoxListWidget.py │ ├── checkBoxTableWidget.py │ ├── circleProfileImage.py │ ├── fileTableDialog.py │ ├── findPathWidget.py │ ├── imageControlWidget.py │ ├── imageMainWidget.py │ ├── imageNavWidget.py │ ├── inputDialog.py │ ├── jsonEditor.py │ ├── linkLabel.py │ ├── modelInputManualDialog.py │ ├── navWidget.py │ ├── normalImageView.py │ ├── notifier.py │ ├── questionTooltipLabel.py │ ├── randomImagePromptGeneratorWidget.py │ ├── readme.txt │ ├── scrollableErrorDialog.py │ ├── searchBar.py │ ├── showingKeyUserInputLineEdit.py │ ├── svgLabel.py │ ├── thumbnailView.py │ ├── toast.py │ └── toolButton.py ├── requirements.txt └── version_info.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: yjg30737 4 | # open_collective: # Replace with a single Open Collective username 5 | custom: https://paypal.me/yjg30737 6 | ko_fi: junggyuyoon 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | # polar: # Replace with a single Polar username 13 | # buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | # thanks_dev: # Replace with a single thanks.dev username 15 | patreon: user?u=121090702 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: BUG 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before bringing up issue, check here: https://github.com/yjg30737/pyqt-openai?tab=readme-ov-file#troubleshooting** 11 | 12 | **Describe the bug** 13 | Description of what the bug is. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Desktop (please complete the following information):** 22 | - OS: [e.g. Windows, MacOS, Linux] 23 | - Python Version [e.g. 3.12.7] (You don't have to fill this if you installed this with Installer) 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Ignore JetBrains IDE configuration 132 | .idea/ 133 | 134 | # pyqt_openai 135 | pyqt_openai/pyqt_openai.ini 136 | pyqt_openai/*.db 137 | pyqt_openai/test/ 138 | pyqt_openai/config.yaml 139 | 140 | .history 141 | .archivist 142 | .ruff_cache 143 | 144 | test 145 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "tamasfe.even-better-toml", 4 | "davidanson.vscode-markdownlint", 5 | "yzhang.markdown-all-in-one", 6 | "irony.qsseditor", 7 | "theqtcompany.qt-core", 8 | "dvratil.vscode-qtdoc", 9 | "seanwu.vscode-qt-for-python", 10 | "theqtcompany.qt-qml", 11 | "theqtcompany.qt-ui", 12 | "charliermarsh.ruff", 13 | "ms-python.vscode-pylance", 14 | "ms-python.python", 15 | "ms-python.debugpy", 16 | "patrick91.python-dependencies-vscode", 17 | "donjayamanne.python-environment-manager", 18 | "twixes.pypi-assistant", 19 | "njqdev.vscode-python-typehint", 20 | "matangover.mypy" 21 | ] 22 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "makefile.configureOnOpen": false, 3 | "mypy.dmypyExecutable": "${workspaceFolder}\\.venv\\Scripts\\dmypy.exe" 4 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | **Thank you for considering contributing to this project!** Contributions are what make the open-source community such an amazing place to learn, inspire, and create. There are many ways to contribute, including reporting issues, improving documentation, submitting bug fixes, and adding new features. 4 | 5 | ## TODO LIST 6 | 7 | The following is a list of planned features and tasks that are still in progress or require assistance. If you are interested, please feel free to take on any task and submit a pull request! 8 | 9 | ### TODO (Essential) 10 | 11 | 1. **Software Maintainer for Mac**: Seeking a maintainer to support and improve the software for Mac users. 12 | 2. **Software Maintainer for Linux**: Seeking a maintainer to support and improve the software for Linux users. 13 | 14 | As of October 14, 2024, Windows has an updater and auto-start features; 15 | however, these features are currently unavailable for Mac and Linux. If you are a user of either platform, please contact me! (I'm Windows user 💻) 16 | 17 | ### TODO (Additional Improvements) 18 | 19 | 1. **Theme Functionality**: Implement a theme switcher to support light and dark modes. 20 | 2. **Language Support**: Add multilingual support for the app. 21 | 3. **Homepage Design**: Customize and improve the design of the homepage. 22 | 4. **Message Property Mapping**: Map each message result to its respective properties for easier management. 23 | 5. **Menu Hiding**: Add the ability to hide certain menus based on user preferences. 24 | 6. **Token Counting**: Implement a feature to count the number of tokens used in messages. 25 | 7. **Linux and macOS Deployment**: Ensure smooth deployment for Linux and macOS systems. 26 | 8. **Issue Support**: Add support for issue tracking and management within the app. 27 | 9. **Discord Admin Recruitment**: Help recruit administrators for the project's Discord channel. 28 | 10. **Release Note Display Dialog**: Show a dialog with the latest release notes upon a successful update. 29 | 11. **Image Import/Export**: Implement functionality to import and export images within grouped categories. 30 | 12. **Prompt Auto-completion**: Add an auto-completion feature for prompts in the messaging interface. 31 | 13. **Tag Assignment Feature**: Enable assigning tags to messages or categories for better organization. 32 | 14. **Menu Bar Hiding**: Add an option to hide the menu bar for a more minimalist interface. 33 | 15. **API Server Development**: Develop an API server to support the app’s back-end functionality. 34 | 16. **Common Database Server Creation**: Create a common database server to store shared data across multiple instances. 35 | 17. **Streamlit GUI**: Build a Streamlit-based GUI for users who want to run the web server. 36 | 18. **Flutter App Development**: Develop a Flutter app for mobile users who want to interact with the platform. 37 | 38 | We welcome all kinds of contributions and look forward to your ideas and improvements. Also you can share your own opinion as well. Such as: 39 | 40 | * I don't think that is a good idea. How about this idea? 41 | * I want to add new language. 42 | 43 | You can share your idea since this is an open source project. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jung Gyu Yoon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include pyqt_openai/ico/* 2 | include pyqt_openai/prompt_res/* 3 | include pyqt_openai/lang/* 4 | include pyqt_openai/img/* 5 | 6 | include pyqt_openai/icon.ico -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: venv run clean format build upload install 2 | 3 | ifeq ($(OS),Windows_NT) 4 | PYTHON=python 5 | VE_PYTHON=venv\Scripts\python 6 | VE_PIP=venv\Scripts\pip 7 | VE_BLACK=venv\Scripts\black 8 | VE_TWINE=venv\Scripts\twine 9 | VE_PYINSTALLER=venv\Scripts\pyinstaller 10 | else 11 | PYTHON=python3 12 | VE_PYTHON=venv/bin/python 13 | VE_PIP=venv/bin/pip 14 | VE_BLACK=venv/bin/black 15 | VE_TWINE=venv/bin/twine 16 | VE_PYINSTALLER=venv/bin/pyinstaller 17 | endif 18 | 19 | venv: 20 | $(PYTHON) -m venv venv 21 | $(VE_PIP) install -r requirements.txt 22 | 23 | run: 24 | $(VE_PYTHON) pyqt_openai/main.py 25 | 26 | clean: 27 | rm -rf dist/ build/ *.egg-info 28 | 29 | format: 30 | $(VE_BLACK) . 31 | 32 | build: 33 | $(VE_PYTHON) -m build 34 | 35 | upload: build 36 | $(VE_TWINE) upload dist/* 37 | 38 | install: 39 | $(VE_PYINSTALLER) main.spec 40 | -------------------------------------------------------------------------------- /har_and_cookies/blackbox.json: -------------------------------------------------------------------------------- 1 | {"validated_value": "00f37b34-a166-4efb-bce5-1312d87f2f94"} -------------------------------------------------------------------------------- /pyqt_openai/aboutDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import datetime 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtGui import QPixmap 7 | from qtpy.QtWidgets import QDialog, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget 8 | 9 | import pyqt_openai 10 | 11 | from pyqt_openai import CONTACT, DEFAULT_APP_ICON, DEFAULT_APP_NAME, LICENSE_URL 12 | from pyqt_openai.lang.translations import LangClass 13 | from pyqt_openai.widgets.linkLabel import LinkLabel 14 | 15 | 16 | class AboutDialog(QDialog): 17 | def __init__( 18 | self, 19 | parent: QWidget | None = None, 20 | ) -> None: 21 | super().__init__(parent) 22 | self.__initUi() 23 | 24 | def __initUi(self): 25 | self.setWindowTitle(LangClass.TRANSLATIONS["About"]) 26 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 27 | 28 | self.__okBtn: QPushButton = QPushButton(LangClass.TRANSLATIONS["OK"]) 29 | self.__okBtn.clicked.connect(self.accept) 30 | 31 | p = QPixmap(DEFAULT_APP_ICON) 32 | logoLbl = QLabel() 33 | logoLbl.setPixmap(p) 34 | 35 | descWidget1 = QLabel() 36 | descWidget1.setText( 37 | f""" 38 |

{DEFAULT_APP_NAME}

39 | Software Version {pyqt_openai.__version__}

40 | © 2023 {datetime.datetime.now().year}. Used under the {pyqt_openai.LICENSE} License.
41 | Copyright (c) {datetime.datetime.now().year} {pyqt_openai.__author__}
42 | """, 43 | ) 44 | 45 | descWidget2 = LinkLabel() 46 | descWidget2.setText(LangClass.TRANSLATIONS["Read MIT License Full Text"]) 47 | descWidget2.setUrl(LICENSE_URL) 48 | 49 | descWidget3 = QLabel() 50 | descWidget3.setText( 51 | f""" 52 |

Contact: {CONTACT}
53 |

Powered by

PySide6, GPT4Free, LiteLLM,
LlamaIndex

54 | """, 55 | ) 56 | 57 | descWidget1.setAlignment(Qt.AlignmentFlag.AlignTop) 58 | descWidget2.setAlignment(Qt.AlignmentFlag.AlignTop) 59 | descWidget3.setAlignment(Qt.AlignmentFlag.AlignTop) 60 | 61 | lay = QVBoxLayout() 62 | lay.addWidget(descWidget1) 63 | lay.addWidget(descWidget2) 64 | lay.addWidget(descWidget3) 65 | lay.setAlignment(Qt.AlignmentFlag.AlignTop) 66 | lay.setContentsMargins(0, 0, 0, 0) 67 | 68 | rightWidget = QWidget() 69 | rightWidget.setLayout(lay) 70 | 71 | lay = QHBoxLayout() 72 | lay.addWidget(logoLbl) 73 | lay.addWidget(rightWidget) 74 | 75 | topWidget = QWidget() 76 | topWidget.setLayout(lay) 77 | 78 | cancelBtn = QPushButton(LangClass.TRANSLATIONS["Cancel"]) 79 | cancelBtn.clicked.connect(self.close) 80 | 81 | lay = QHBoxLayout() 82 | lay.addWidget(self.__okBtn) 83 | lay.addWidget(cancelBtn) 84 | lay.setAlignment(Qt.AlignmentFlag.AlignRight) 85 | lay.setContentsMargins(0, 0, 0, 0) 86 | 87 | okCancelWidget = QWidget() 88 | okCancelWidget.setLayout(lay) 89 | 90 | lay = QVBoxLayout() 91 | lay.addWidget(topWidget) 92 | lay.addWidget(okCancelWidget) 93 | 94 | self.setLayout(lay) 95 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/chatHome.py: -------------------------------------------------------------------------------- 1 | # Currently this page is home page of the application. 2 | from __future__ import annotations 3 | 4 | from qtpy.QtCore import Qt 5 | from qtpy.QtGui import QFont, QPixmap 6 | from qtpy.QtWidgets import QLabel, QScrollArea, QVBoxLayout, QWidget 7 | 8 | from pyqt_openai import ( 9 | DEFAULT_APP_NAME, 10 | LARGE_LABEL_PARAM, 11 | MEDIUM_LABEL_PARAM, 12 | QUICKSTART_MANUAL_URL, 13 | ) 14 | from pyqt_openai.lang.translations import LangClass 15 | from pyqt_openai.widgets.linkLabel import LinkLabel 16 | 17 | 18 | class ChatHome(QScrollArea): 19 | def __init__(self, parent=None): 20 | super().__init__(parent) 21 | self.__initUi() 22 | 23 | def __initUi(self): 24 | title = QLabel(f"Welcome to {DEFAULT_APP_NAME}!", self) 25 | title.setFont(QFont(*LARGE_LABEL_PARAM)) 26 | title.setAlignment(Qt.AlignmentFlag.AlignCenter) 27 | 28 | description = QLabel( 29 | LangClass.TRANSLATIONS["Enjoy convenient chatting, all day long!"], 30 | ) 31 | 32 | self.__quickStartManualLbl = LinkLabel() 33 | self.__quickStartManualLbl.setText(LangClass.TRANSLATIONS["Quick Start Manual"]) 34 | self.__quickStartManualLbl.setUrl(QUICKSTART_MANUAL_URL) 35 | self.__quickStartManualLbl.setFont(QFont(*MEDIUM_LABEL_PARAM)) 36 | self.__quickStartManualLbl.setAlignment(Qt.AlignmentFlag.AlignCenter) 37 | 38 | self.__background_image = QLabel() 39 | 40 | description.setFont(QFont(*MEDIUM_LABEL_PARAM)) 41 | description.setAlignment(Qt.AlignmentFlag.AlignCenter) 42 | 43 | lay = QVBoxLayout() 44 | lay.addWidget(title) 45 | lay.addWidget(description) 46 | lay.addWidget(self.__quickStartManualLbl) 47 | lay.addWidget(self.__background_image) 48 | lay.setAlignment(Qt.AlignmentFlag.AlignCenter) 49 | self.setLayout(lay) 50 | 51 | mainWidget = QWidget() 52 | mainWidget.setLayout(lay) 53 | self.setWidget(mainWidget) 54 | self.setWidgetResizable(True) 55 | 56 | def setPixmap(self, filename): 57 | self.__background_image.setPixmap(QPixmap(filename)) 58 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/chatUnit.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pyperclip 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtGui import QPalette 7 | from qtpy.QtWidgets import ( 8 | QHBoxLayout, 9 | QLabel, 10 | QSizePolicy, 11 | QSpacerItem, 12 | QVBoxLayout, 13 | QWidget, 14 | ) 15 | 16 | from pyqt_openai import DEFAULT_ICON_SIZE, ICON_COPY 17 | from pyqt_openai.chat_widget.center.messageTextBrowser import MessageTextBrowser 18 | from pyqt_openai.widgets.button import Button 19 | from pyqt_openai.widgets.circleProfileImage import RoundedImage 20 | 21 | 22 | class ChatUnit(QWidget): 23 | def __init__(self, parent=None): 24 | super().__init__(parent) 25 | self.__initUi() 26 | 27 | def __initUi(self): 28 | self._menuWidget = QWidget() 29 | lay = QHBoxLayout() 30 | 31 | self._icon = RoundedImage() 32 | self._icon.setMaximumSize(*DEFAULT_ICON_SIZE) 33 | self._icon.setStyleSheet("margin-right: 3px;") 34 | 35 | self._nameLbl = QLabel() 36 | 37 | self._copyBtn = Button() 38 | self._copyBtn.setStyleAndIcon(ICON_COPY) 39 | self._copyBtn.clicked.connect(self.__copy) 40 | 41 | lay.addWidget(self._icon) 42 | lay.addWidget(self._nameLbl) 43 | lay.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Policy.MinimumExpanding)) 44 | lay.addWidget(self._copyBtn) 45 | lay.setContentsMargins(2, 2, 2, 2) 46 | lay.setSpacing(1) 47 | 48 | self._menuWidget.setLayout(lay) 49 | self._menuWidget.setMaximumHeight(self._menuWidget.sizeHint().height()) 50 | 51 | self._lbl = MessageTextBrowser() 52 | self._lbl.setAlignment(Qt.AlignmentFlag.AlignLeft) 53 | 54 | lay = QVBoxLayout() 55 | lay.addWidget(self._menuWidget) 56 | lay.addWidget(self._lbl) 57 | lay.setContentsMargins(0, 0, 0, 0) 58 | lay.setSpacing(0) 59 | 60 | self.setLayout(lay) 61 | 62 | self.setBackgroundRole(QPalette.ColorRole.Base) 63 | self.setAutoFillBackground(True) 64 | 65 | self._lbl.adjustBrowserHeight() 66 | 67 | def __copy(self): 68 | pyperclip.copy(self.getText()) 69 | 70 | def setText(self, text: str): 71 | self._lbl.setText(text) 72 | self._lbl.adjustBrowserHeight() 73 | 74 | def getText(self): 75 | return self._lbl.toPlainText() 76 | 77 | def getIcon(self): 78 | return self._icon.getImage() 79 | 80 | def setIcon(self, filename): 81 | self._icon.setImage(filename) 82 | 83 | def getLbl(self): 84 | return self._lbl 85 | 86 | def getMenuWidget(self): 87 | return self._menuWidget 88 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/commandCompleter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt, Signal 4 | from qtpy.QtWidgets import ( 5 | QHeaderView, 6 | QScrollArea, 7 | QStyle, 8 | QStyledItemDelegate, 9 | QTableWidget, 10 | QTableWidgetItem, 11 | ) 12 | 13 | 14 | class CommandCompleterTableWidgetDelegate(QStyledItemDelegate): 15 | def paint(self, painter, option, index): 16 | # Check if the item is selected 17 | if option.state & QStyle.StateFlag.State_Active: 18 | # Set the background color for selected item 19 | option.palette.setColor(option.palette.Highlight, Qt.GlobalColor.lightGray) 20 | 21 | # Call the base paint method 22 | super().paint(painter, option, index) 23 | 24 | 25 | class CommandCompleterTableWidget(QTableWidget): 26 | showText = Signal(str) 27 | 28 | def __init__(self, parent=None): 29 | super().__init__(parent) 30 | self.__initUi() 31 | 32 | def __initUi(self): 33 | self.setWindowFlags(Qt.WindowType.ToolTip) 34 | self.setColumnCount(2) 35 | 36 | self.horizontalHeader().setVisible(False) 37 | self.verticalHeader().setVisible(False) 38 | self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) 39 | 40 | self.clicked.connect(self.__showText) 41 | 42 | delegate = CommandCompleterTableWidgetDelegate() 43 | 44 | self.setItemDelegate(delegate) 45 | 46 | def searchTexts(self, text): 47 | matched_texts_lst = [] 48 | for i in range(self.rowCount()): 49 | widget = self.item(i, 0) 50 | if widget: 51 | widget_text = widget.text() 52 | if text.strip() != "": 53 | idx = widget_text.lower().find(text.lower()) 54 | if idx != -1: 55 | matched_texts_lst.append(text) 56 | self.showRow(i) 57 | else: 58 | self.hideRow(i) 59 | else: 60 | self.hideRow(i) 61 | return len(matched_texts_lst) > 0 62 | 63 | def addPromptCommand(self, prompt_command_lst: list): 64 | for prompt_command_unit in prompt_command_lst: 65 | name = prompt_command_unit["name"] 66 | value = prompt_command_unit["value"] 67 | 68 | item1 = QTableWidgetItem() 69 | item1.setText(name) 70 | item1.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 71 | 72 | item2 = QTableWidgetItem() 73 | item2.setText(value) 74 | item2.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 75 | 76 | row_idx = self.rowCount() 77 | self.setRowCount(row_idx + 1) 78 | self.setItem(row_idx, 0, item1) 79 | self.setItem(row_idx, 1, item2) 80 | self.hideRow(row_idx) 81 | 82 | def __showText(self, idx): 83 | widget = self.indexWidget(idx) 84 | if widget: 85 | self.showText.emit(widget.text()) 86 | 87 | 88 | class CommandCompleter(QScrollArea): 89 | showText = Signal(str) 90 | 91 | def __init__(self, parent=None): 92 | super().__init__(parent) 93 | self.__initUi() 94 | 95 | def __initUi(self): 96 | self.__completerTable = CommandCompleterTableWidget() 97 | self.__completerTable.showText.connect(self.__showText) 98 | 99 | self.setVisible(False) 100 | self.setWidgetResizable(True) 101 | 102 | self.setWidget(self.__completerTable) 103 | 104 | def __showText(self, text): 105 | self.setVisible(False) 106 | self.showText.emit(text) 107 | 108 | def addPromptCommand(self, prompt_command_lst: list): 109 | self.__completerTable.addPromptCommand(prompt_command_lst) 110 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/commandSuggestionWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Signal 4 | from qtpy.QtWidgets import QLabel, QListWidget, QVBoxLayout, QWidget 5 | 6 | from pyqt_openai.lang.translations import LangClass 7 | 8 | 9 | class CommandSuggestionWidget(QWidget): 10 | toggleCommandSuggestion = Signal(bool) 11 | 12 | def __init__(self, parent=None): 13 | super().__init__(parent) 14 | self.__initUi() 15 | 16 | def __initUi(self): 17 | self.__commandList = QListWidget() 18 | self.__commandList.currentRowChanged.connect(self.onCountChanged) 19 | 20 | lay = QVBoxLayout() 21 | lay.addWidget(QLabel(LangClass.TRANSLATIONS["Command List"])) 22 | lay.addWidget(self.__commandList) 23 | lay.setContentsMargins(0, 5, 0, 0) 24 | 25 | self.setLayout(lay) 26 | 27 | def getCommandList(self): 28 | return self.__commandList 29 | 30 | def onCountChanged(self): 31 | itemHeight = self.__commandList.sizeHintForRow(0) 32 | itemCount = self.__commandList.count() 33 | contentHeight = itemHeight * itemCount 34 | scrollbarHeight = self.__commandList.verticalScrollBar().sizeHint().height() 35 | totalHeight = contentHeight + itemHeight 36 | self.setMaximumHeight(totalHeight + scrollbarHeight) 37 | 38 | def showEvent(self, event): 39 | self.toggleCommandSuggestion.emit(True) 40 | 41 | def hideEvent(self, event): 42 | self.toggleCommandSuggestion.emit(False) 43 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/menuWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Signal 6 | from qtpy.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget 7 | 8 | from pyqt_openai import DEFAULT_SHORTCUT_FIND_CLOSE, ICON_CLOSE 9 | from pyqt_openai.chat_widget.center.findTextWidget import FindTextWidget 10 | from pyqt_openai.lang.translations import LangClass 11 | from pyqt_openai.widgets.button import Button 12 | 13 | if TYPE_CHECKING: 14 | from pyqt_openai.chat_widget.center.chatBrowser import ChatBrowser 15 | 16 | 17 | class MenuWidget(QWidget): 18 | onMenuCloseClicked = Signal() 19 | 20 | def __init__(self, widget: ChatBrowser, parent=None): 21 | super().__init__(parent) 22 | self.__initUi(widget=widget) 23 | 24 | def __initUi(self, widget): 25 | self.__titleLbl = QLabel(LangClass.TRANSLATIONS["Title"]) 26 | self.__findTextWidget = FindTextWidget(widget) 27 | self.__chatBrowser = widget 28 | 29 | self.__closeBtn = Button() 30 | self.__closeBtn.clicked.connect(self.__onMenuCloseClicked) 31 | self.__closeBtn.setShortcut(DEFAULT_SHORTCUT_FIND_CLOSE) 32 | self.__closeBtn.setStyleAndIcon(ICON_CLOSE) 33 | 34 | self.__closeBtn.setToolTip( 35 | LangClass.TRANSLATIONS["Close"] + f" ({DEFAULT_SHORTCUT_FIND_CLOSE})", 36 | ) 37 | 38 | lay = QVBoxLayout() 39 | lay.addWidget(self.__titleLbl) 40 | lay.addWidget(self.__findTextWidget) 41 | lay.addWidget(self.__closeBtn) 42 | lay.setContentsMargins(0, 0, 0, 0) 43 | 44 | mainWidget = QWidget() 45 | mainWidget.setLayout(lay) 46 | 47 | lay = QHBoxLayout() 48 | lay.addWidget(mainWidget) 49 | lay.addWidget(self.__closeBtn) 50 | lay.setContentsMargins(4, 4, 4, 4) 51 | 52 | self.setLayout(lay) 53 | 54 | def setTitle(self, title): 55 | self.__titleLbl.setText(title) 56 | 57 | def showEvent(self, event): 58 | self.__findTextWidget.getLineEdit().setFocus() 59 | self.__findTextWidget.initFind(self.__findTextWidget.getLineEdit().text()) 60 | super().showEvent(event) 61 | 62 | def __onMenuCloseClicked(self): 63 | self.__findTextWidget.clearFormatting() 64 | self.onMenuCloseClicked.emit() 65 | self.hide() 66 | 67 | def getFindTextWidget(self): 68 | return self.__findTextWidget 69 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/messageTextBrowser.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | 5 | # from qtpy.QtWidgets import QApplication, QWidget, QVBoxLayout 6 | from qtpy.QtGui import QColor, QDesktopServices, QPalette 7 | from qtpy.QtWidgets import QTextBrowser 8 | 9 | from pyqt_openai import INDENT_SIZE, MESSAGE_MAXIMUM_HEIGHT, MESSAGE_PADDING 10 | 11 | 12 | class MessageTextBrowser(QTextBrowser): 13 | def __init__(self, parent=None): 14 | super().__init__(parent) 15 | 16 | # Make the remote links clickable 17 | self.anchorClicked.connect(self.on_anchor_clicked) 18 | self.setOpenExternalLinks(True) 19 | self.__initUi() 20 | 21 | def on_anchor_clicked(self, url): 22 | QDesktopServices.openUrl(url) 23 | 24 | def __initUi(self): 25 | # Transparent background 26 | palette = self.palette() 27 | palette.setColor(QPalette.Base, QColor(0, 0, 0, 0)) 28 | self.setPalette(palette) 29 | 30 | # Remove edge 31 | self.setFrameShape(QTextBrowser.NoFrame) 32 | 33 | # Padding 34 | self.document().setDocumentMargin(MESSAGE_PADDING) 35 | 36 | self.setContentsMargins(0, 0, 0, 0) 37 | 38 | def setJson(self, json_str): 39 | try: 40 | json_data = json.loads(json_str) 41 | pretty_json = json.dumps(json_data, indent=INDENT_SIZE) 42 | self.setPlainText(pretty_json) 43 | except json.JSONDecodeError as e: 44 | self.setPlainText(f"Error decoding JSON: {e}") 45 | 46 | def adjustBrowserHeight(self): 47 | document_height = self.document().size().height() 48 | max_height = MESSAGE_MAXIMUM_HEIGHT 49 | 50 | if document_height < max_height: 51 | self.setMinimumHeight(int(document_height)) 52 | else: 53 | self.setMinimumHeight(int(max_height)) 54 | self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum()) 55 | 56 | def setMarkdown(self, markdown: str) -> None: 57 | super().setMarkdown(markdown) 58 | # Convert markdown to HTML using QTextDocument 59 | 60 | # document = QTextDocument() 61 | # document.setMarkdown(markdown) 62 | # html_text = document.toHtml() 63 | # with open("test.html", "w") as f: 64 | # f.write(html_text) 65 | # # 66 | # # # Customize the converted HTML (e.g., add style tags) 67 | # custom_html = f""" 68 | # {html_text} 89 | # """ 90 | # self.setHtml(custom_html) 91 | # 92 | # def eventFilter(self, obj, event): 93 | # print(obj, int(event.type())) 94 | # return super().eventFilter(obj, event) 95 | 96 | 97 | # 98 | # def main(): 99 | # app = QApplication([]) 100 | # 101 | # window = QWidget() 102 | # layout = QVBoxLayout() 103 | # 104 | # text_browser = MessageTextBrowser() 105 | # 106 | # markdown_text = """ 107 | # https://www.google.com 108 | # """ 109 | # text_browser.setMarkdown(markdown_text) 110 | # 111 | # layout.addWidget(text_browser) 112 | # window.setLayout(layout) 113 | # window.show() 114 | # 115 | # app.exec() 116 | # 117 | # 118 | # if __name__ == "__main__": 119 | # main() 120 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/realtimeApiWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtGui import QFont 5 | from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget 6 | 7 | from pyqt_openai import LARGE_LABEL_PARAM 8 | 9 | 10 | class RealtimeApiWidget(QWidget): 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | self.initUI() 14 | 15 | def initUI(self): 16 | lay = QVBoxLayout() 17 | 18 | title = QLabel("Coming Soon...", self) 19 | title.setFont(QFont(*LARGE_LABEL_PARAM)) 20 | title.setAlignment(Qt.AlignmentFlag.AlignCenter) 21 | 22 | lay.addWidget(title) 23 | 24 | self.setLayout(lay) 25 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/responseInfoDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtWidgets import QDialog, QFormLayout, QLabel, QPushButton 7 | 8 | from pyqt_openai.lang.translations import LangClass 9 | from pyqt_openai.util.common import getSeparator 10 | 11 | if TYPE_CHECKING: 12 | from pyqt_openai.models import ChatMessageContainer 13 | 14 | 15 | class ResponseInfoDialog(QDialog): 16 | def __init__(self, result_info: ChatMessageContainer, parent=None): 17 | super().__init__(parent) 18 | self.__initVal(result_info) 19 | self.__initUi() 20 | 21 | def __initVal(self, result_info): 22 | self.__result_info = result_info 23 | 24 | def __initUi(self): 25 | self.setWindowTitle(LangClass.TRANSLATIONS["Message Result"]) 26 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 27 | 28 | lbls = [] 29 | for k, v in self.__result_info.get_items(excludes=["content"]): 30 | if k == "favorite": 31 | lbls.append(QLabel(f'{k}: {"Yes" if v else "No"}')) 32 | else: 33 | lbls.append(QLabel(f"{k}: {v}")) 34 | 35 | sep = getSeparator("horizontal") 36 | 37 | okBtn = QPushButton("OK") 38 | okBtn.clicked.connect(self.accept) 39 | 40 | lay = QFormLayout() 41 | for lbl in lbls: 42 | lay.addWidget(lbl) 43 | lay.addWidget(sep) 44 | lay.addWidget(okBtn) 45 | 46 | self.setLayout(lay) 47 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/textEditPrompt.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | from typing import TYPE_CHECKING 5 | 6 | from qtpy.QtCore import Qt, Signal 7 | from qtpy.QtWidgets import QTextEdit 8 | 9 | from pyqt_openai import IMAGE_FILE_EXT_LIST 10 | 11 | if TYPE_CHECKING: 12 | from qtpy.QtCore import QMimeData 13 | 14 | 15 | class TextEditPrompt(QTextEdit): 16 | returnPressed = Signal() 17 | sendSuggestionWidget = Signal(str) 18 | moveCursorToOtherPrompt = Signal(str) 19 | handleDrop = Signal(list) 20 | 21 | def __init__(self, parent=None): 22 | super().__init__(parent) 23 | self.__initVal() 24 | self.__initUi() 25 | 26 | def __initVal(self): 27 | self.__commandSuggestionEnabled = False 28 | self.__executeEnabled = True 29 | 30 | def __initUi(self): 31 | self.setAcceptRichText(False) 32 | 33 | def setExecuteEnabled(self, f): 34 | self.__executeEnabled = f 35 | 36 | def setCommandSuggestionEnabled(self, f): 37 | self.__commandSuggestionEnabled = f 38 | 39 | def sendMessage(self): 40 | self.returnPressed.emit() 41 | 42 | def keyPressEvent(self, event): 43 | # Send key events to the suggestion widget if enabled 44 | if self.__commandSuggestionEnabled: 45 | if event.key() == Qt.Key.Key_Up: 46 | self.sendSuggestionWidget.emit("up") 47 | elif event.key() == Qt.Key.Key_Down: 48 | self.sendSuggestionWidget.emit("down") 49 | elif event.modifiers() == Qt.KeyboardModifier.ControlModifier: 50 | if event.key() == Qt.Key.Key_Up: 51 | self.moveCursorToOtherPrompt.emit("up") 52 | return None 53 | if event.key() == Qt.Key.Key_Down: 54 | self.moveCursorToOtherPrompt.emit("down") 55 | return None 56 | return super().keyPressEvent(event) 57 | 58 | if ( 59 | event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter 60 | ) and self.__executeEnabled: 61 | if event.modifiers() == Qt.KeyboardModifier.ShiftModifier: 62 | return super().keyPressEvent(event) 63 | if self.__commandSuggestionEnabled: 64 | self.sendSuggestionWidget.emit("enter") 65 | else: 66 | self.sendMessage() 67 | else: 68 | return super().keyPressEvent(event) 69 | 70 | def focusInEvent(self, event): 71 | self.setCursorWidth(1) 72 | return super().focusInEvent(event) 73 | 74 | def focusOutEvent(self, event): 75 | self.setCursorWidth(0) 76 | return super().focusInEvent(event) 77 | 78 | def dropEvent(self, e): 79 | if e.mimeData().hasUrls(): 80 | urls = [url.toLocalFile() for url in e.mimeData().urls()] 81 | self.handleDrop.emit(urls) 82 | e.accept() 83 | else: 84 | e.ignore() 85 | 86 | def insertFromMimeData(self, source: QMimeData): 87 | paths = [] 88 | for url in source.urls(): 89 | if url.isLocalFile(): 90 | file_path = url.toLocalFile() 91 | if Path(file_path).suffix in IMAGE_FILE_EXT_LIST: 92 | paths.append(file_path) 93 | if paths and len(paths) > 0: 94 | self.handleDrop.emit(paths) 95 | else: 96 | super().insertFromMimeData(source) 97 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/center/userChatUnit.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pyqt_openai.chat_widget.center.chatUnit import ChatUnit 4 | 5 | 6 | class UserChatUnit(ChatUnit): 7 | def __init__(self, parent=None): 8 | super().__init__(parent) 9 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/left_sidebar/exportDialog.py: -------------------------------------------------------------------------------- 1 | """This dialog is for exporting conversation threads selected by the user from the history.""" 2 | from __future__ import annotations 3 | 4 | from typing import TYPE_CHECKING, Any 5 | 6 | from qtpy.QtCore import Qt 7 | from qtpy.QtWidgets import QCheckBox, QDialog, QDialogButtonBox, QLabel, QTableWidgetItem, QVBoxLayout 8 | 9 | from pyqt_openai import THREAD_ORDERBY 10 | from pyqt_openai.lang.translations import LangClass 11 | from pyqt_openai.widgets.checkBoxTableWidget import CheckBoxTableWidget 12 | 13 | if TYPE_CHECKING: 14 | from qtpy.QtWidgets import QWidget 15 | 16 | 17 | class ExportDialog(QDialog): 18 | def __init__( 19 | self, 20 | columns: list[str], 21 | data: list[dict[str, Any]], 22 | sort_by: str = THREAD_ORDERBY, 23 | parent: QWidget | None = None, 24 | ): 25 | super().__init__(parent) 26 | self.__initVal(columns, data, sort_by) 27 | self.__initUi() 28 | 29 | def __initVal( 30 | self, 31 | columns: list[str], 32 | data: list[dict[str, Any]], 33 | sort_by: str, 34 | ): 35 | self.__columns = columns 36 | self.__data = data 37 | self.__sort_by = sort_by 38 | 39 | def __initUi(self): 40 | self.setWindowTitle(LangClass.TRANSLATIONS["Export"]) 41 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 42 | 43 | self.__checkBoxTableWidget: CheckBoxTableWidget = CheckBoxTableWidget() 44 | self.__checkBoxTableWidget.setHorizontalHeaderLabels(self.__columns) 45 | self.__checkBoxTableWidget.setRowCount(len(self.__data)) 46 | 47 | for r_idx, r in enumerate(self.__data): 48 | for c_idx, c in enumerate(self.__columns): 49 | v = r[c] 50 | self.__checkBoxTableWidget.setItem( 51 | r_idx, c_idx + 1, QTableWidgetItem(str(v)), 52 | ) 53 | 54 | self.__checkBoxTableWidget.resizeColumnsToContents() 55 | self.__checkBoxTableWidget.setSortingEnabled(True) 56 | if self.__sort_by in self.__columns: 57 | self.__checkBoxTableWidget.sortByColumn( 58 | self.__columns.index(self.__sort_by) + 1, Qt.SortOrder.DescendingOrder, 59 | ) 60 | 61 | # Dialog buttons 62 | buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) 63 | buttonBox.accepted.connect(self.accept) 64 | buttonBox.rejected.connect(self.reject) 65 | 66 | allCheckBox = QCheckBox(LangClass.TRANSLATIONS["Select All"]) 67 | allCheckBox.stateChanged.connect( 68 | self.__checkBoxTableWidget.toggleState, 69 | ) # if allChkBox is checked, tablewidget checkboxes will also be checked 70 | 71 | lay = QVBoxLayout() 72 | lay.addWidget( 73 | QLabel(LangClass.TRANSLATIONS["Select the threads you want to export."]), 74 | ) 75 | lay.addWidget(allCheckBox) 76 | lay.addWidget(self.__checkBoxTableWidget) 77 | lay.addWidget(buttonBox) 78 | self.setLayout(lay) 79 | 80 | def getSelectedIds(self) -> list[str]: 81 | ids = [ 82 | self.__checkBoxTableWidget.item(r, 1).text() # type: ignore[union-attr] 83 | for r in self.__checkBoxTableWidget.getCheckedRows() 84 | ] 85 | return ids 86 | 87 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/left_sidebar/selectChatImportTypeDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import ( 5 | QButtonGroup, 6 | QDialog, 7 | QDialogButtonBox, 8 | QGroupBox, 9 | QRadioButton, 10 | QVBoxLayout, 11 | ) 12 | 13 | from pyqt_openai.lang.translations import LangClass 14 | 15 | 16 | class SelectChatImportTypeDialog(QDialog): 17 | def __init__(self, parent=None): 18 | super().__init__(parent) 19 | self.__initVal() 20 | self.__initUi() 21 | 22 | def __initVal(self): 23 | self.__selected_import_type = None 24 | 25 | def __initUi(self): 26 | self.setWindowTitle(LangClass.TRANSLATIONS["Import From..."]) 27 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 28 | 29 | self.__generalRadBtn = QRadioButton(LangClass.TRANSLATIONS["General"]) 30 | self.__chatGptRadBtn = QRadioButton(LangClass.TRANSLATIONS["ChatGPT"]) 31 | 32 | self.__generalRadBtn.setChecked(True) 33 | 34 | self.__buttonGroup = QButtonGroup() 35 | self.__buttonGroup.addButton(self.__generalRadBtn, 1) 36 | self.__buttonGroup.addButton(self.__chatGptRadBtn, 2) 37 | 38 | lay = QVBoxLayout() 39 | lay.addWidget(self.__generalRadBtn) 40 | lay.addWidget(self.__chatGptRadBtn) 41 | lay.setAlignment(Qt.AlignmentFlag.AlignTop) 42 | 43 | importTypeGrpBox = QGroupBox(LangClass.TRANSLATIONS["Import Type"]) 44 | importTypeGrpBox.setLayout(lay) 45 | 46 | buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) 47 | buttonBox.accepted.connect(self.accept) 48 | buttonBox.rejected.connect(self.reject) 49 | 50 | lay = QVBoxLayout() 51 | lay.addWidget(importTypeGrpBox) 52 | lay.addWidget(buttonBox) 53 | 54 | self.setLayout(lay) 55 | 56 | def getImportType(self): 57 | selected_button_id = self.__buttonGroup.checkedId() 58 | if selected_button_id == 1: 59 | return "general" 60 | if selected_button_id == 2: 61 | return "chatgpt" 62 | return None 63 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/llamaIndexThread.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from llama_index.core.base.response.schema import StreamingResponse 4 | from qtpy.QtCore import QThread, Signal 5 | 6 | from pyqt_openai.models import ChatMessageContainer 7 | 8 | 9 | # Should combine with ChatThread 10 | class LlamaIndexThread(QThread): 11 | replyGenerated = Signal(str, bool, ChatMessageContainer) 12 | streamFinished = Signal(ChatMessageContainer) 13 | 14 | def __init__(self, input_args, info: ChatMessageContainer, wrapper, query_text): 15 | super().__init__() 16 | self.__input_args = input_args 17 | self.__stop = False 18 | 19 | self.__info = info 20 | self.__info.role = "assistant" 21 | 22 | self.__wrapper = wrapper 23 | self.__query_text = query_text 24 | 25 | def stop(self): 26 | self.__stop = True 27 | 28 | def run(self): 29 | try: 30 | resp = self.__wrapper.get_response(self.__query_text) 31 | f = isinstance(resp, StreamingResponse) 32 | if f: 33 | for chunk in resp.response_gen: 34 | if self.__stop: 35 | self.__info.finish_reason = "stopped by user" 36 | self.streamFinished.emit(self.__info) 37 | break 38 | self.replyGenerated.emit(chunk, True, self.__info) 39 | else: 40 | self.__info.content = resp.response 41 | # self.__info.prompt_tokens = "" 42 | # self.__info.completion_tokens = "" 43 | # self.__info.total_tokens = "" 44 | 45 | self.__info.finish_reason = "stop" 46 | 47 | if self.__input_args["stream"]: 48 | self.streamFinished.emit(self.__info) 49 | else: 50 | self.replyGenerated.emit(self.__info.content, False, self.__info) 51 | except Exception as e: 52 | self.__info.finish_reason = "Error" 53 | self.__info.content = f'

{e}

' 54 | self.replyGenerated.emit(self.__info.content, False, self.__info) 55 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/importPromptManualDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import QDialog, QLabel, QPushButton, QVBoxLayout 5 | 6 | from pyqt_openai import ( 7 | AWESOME_CHATGPT_PROMPTS_URL, 8 | SENTENCE_PROMPT_GROUP_SAMPLE, 9 | ) 10 | from pyqt_openai.chat_widget.prompt_gen_widget.promptCsvRightFormSampleDialog import ( 11 | PromptCSVRightFormSampleDialog, 12 | ) 13 | from pyqt_openai.lang.translations import LangClass 14 | from pyqt_openai.util.common import showJsonSample 15 | from pyqt_openai.widgets.jsonEditor import JSONEditor 16 | 17 | 18 | class ImportPromptManualDialog(QDialog): 19 | def __init__(self, parent=None): 20 | super().__init__(parent) 21 | self.__initUi() 22 | 23 | def __initUi(self): 24 | self.setWindowTitle(LangClass.TRANSLATIONS["Import Manual"]) 25 | self.__jsonSampleWidget = JSONEditor() 26 | jsonRightFormBtn = QPushButton( 27 | LangClass.TRANSLATIONS[ 28 | "What is the right form of json to be imported?" 29 | ], 30 | ) 31 | 32 | csvRightFormBtn = QPushButton( 33 | LangClass.TRANSLATIONS[ 34 | "What is the right form of csv to be imported?" 35 | ], 36 | ) 37 | 38 | jsonRightFormBtn.clicked.connect(self.__showJsonSample) 39 | csvRightFormBtn.clicked.connect(self.__showCSVSample) 40 | 41 | awesomeChatGptPromptDownloadLink = QLabel(f'Try downloading Awesome ChatGPT prompts and import! 😊') 42 | awesomeChatGptPromptDownloadLink.setTextInteractionFlags( 43 | Qt.TextInteractionFlag.TextBrowserInteraction, 44 | ) 45 | awesomeChatGptPromptDownloadLink.setOpenExternalLinks(True) # Enable hyperlink functionality. 46 | 47 | lay = QVBoxLayout() 48 | lay.addWidget(jsonRightFormBtn) 49 | lay.addWidget(csvRightFormBtn) 50 | lay.addWidget(awesomeChatGptPromptDownloadLink) 51 | self.setLayout(lay) 52 | 53 | def __showJsonSample(self): 54 | showJsonSample(self.__jsonSampleWidget, SENTENCE_PROMPT_GROUP_SAMPLE) 55 | 56 | def __showCSVSample(self): 57 | dialog = PromptCSVRightFormSampleDialog(self) 58 | dialog.exec() 59 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/promptCsvRightFormSampleDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtGui import QPainter 4 | from qtpy.QtWidgets import QDialog, QLabel, QVBoxLayout 5 | 6 | from pyqt_openai import IMAGE_IMPORT_PROMPT_WITH_CSV_RIGHT_FORM 7 | from pyqt_openai.lang.translations import LangClass 8 | from pyqt_openai.widgets.normalImageView import NormalImageView 9 | 10 | 11 | class PromptCSVRightFormSampleDialog(QDialog): 12 | def __init__(self, parent=None): 13 | super().__init__(parent) 14 | self.__initUi() 15 | 16 | def __initUi(self): 17 | self.setWindowTitle(LangClass.TRANSLATIONS["CSV Right Form Sample"]) 18 | # Add image 19 | view = NormalImageView() 20 | view.setFilename(IMAGE_IMPORT_PROMPT_WITH_CSV_RIGHT_FORM) 21 | # Anti-aliasing 22 | view.setRenderHint(QPainter.RenderHint.Antialiasing, True) 23 | 24 | lay = QVBoxLayout() 25 | lay.addWidget(view) 26 | lay.addWidget(QLabel("This is from awesome_chatgpt_prompt.csv file from huggingface.")) 27 | 28 | self.setLayout(lay) 29 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/promptEntryDirectInputDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import ( 5 | QDialog, 6 | QHBoxLayout, 7 | QLineEdit, 8 | QMessageBox, 9 | QPlainTextEdit, 10 | QPushButton, 11 | QVBoxLayout, 12 | QWidget, 13 | ) 14 | 15 | from pyqt_openai.lang.translations import LangClass 16 | from pyqt_openai.util.common import getSeparator, is_prompt_entry_name_valid 17 | 18 | 19 | class PromptEntryDirectInputDialog(QDialog): 20 | def __init__(self, group_id, parent=None): 21 | super().__init__(parent) 22 | self.__initVal(group_id) 23 | self.__initUi() 24 | 25 | def __initVal(self, group_id): 26 | self.__group_id = group_id 27 | 28 | def __initUi(self): 29 | self.setWindowTitle(LangClass.TRANSLATIONS["New Prompt"]) 30 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 31 | 32 | self.__name = QLineEdit() 33 | self.__name.setPlaceholderText(LangClass.TRANSLATIONS["Name"]) 34 | 35 | self.__content = QPlainTextEdit() 36 | self.__content.setPlaceholderText(LangClass.TRANSLATIONS["Content"]) 37 | 38 | sep = getSeparator("horizontal") 39 | 40 | self.__okBtn = QPushButton(LangClass.TRANSLATIONS["OK"]) 41 | self.__okBtn.clicked.connect(self.__accept) 42 | 43 | cancelBtn = QPushButton(LangClass.TRANSLATIONS["Cancel"]) 44 | cancelBtn.clicked.connect(self.close) 45 | 46 | lay = QHBoxLayout() 47 | lay.addWidget(self.__okBtn) 48 | lay.addWidget(cancelBtn) 49 | lay.setAlignment(Qt.AlignmentFlag.AlignRight) 50 | lay.setContentsMargins(0, 0, 0, 0) 51 | 52 | okCancelWidget = QWidget() 53 | okCancelWidget.setLayout(lay) 54 | 55 | lay = QVBoxLayout() 56 | lay.addWidget(self.__name) 57 | lay.addWidget(self.__content) 58 | lay.addWidget(sep) 59 | lay.addWidget(okCancelWidget) 60 | 61 | self.setLayout(lay) 62 | 63 | def getAct(self): 64 | return self.__name.text() 65 | 66 | def getPrompt(self): 67 | return self.__content.toPlainText() 68 | 69 | def __accept(self): 70 | exists_f = is_prompt_entry_name_valid(self.__group_id, self.__name.text()) 71 | if exists_f: 72 | self.__name.setFocus() 73 | QMessageBox.warning( # type: ignore[call-arg] 74 | self, 75 | LangClass.TRANSLATIONS["Warning"], 76 | LangClass.TRANSLATIONS["Entry name already exists."], 77 | ) 78 | return 79 | self.accept() 80 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/promptGeneratorWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pyperclip 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtWidgets import ( 7 | QLabel, 8 | QPushButton, 9 | QScrollArea, 10 | QSplitter, 11 | QTabWidget, 12 | QTextBrowser, 13 | QVBoxLayout, 14 | QWidget, 15 | ) 16 | 17 | from pyqt_openai.chat_widget.prompt_gen_widget.promptPage import PromptPage 18 | from pyqt_openai.lang.translations import LangClass 19 | 20 | 21 | class PromptGeneratorWidget(QScrollArea): 22 | def __init__(self, parent=None): 23 | super().__init__(parent) 24 | self.__initUi() 25 | 26 | def __initUi(self): 27 | promptLbl = QLabel(LangClass.TRANSLATIONS["Prompt"]) 28 | 29 | formPage = PromptPage(prompt_type="form") 30 | formPage.updated.connect(self.__textChanged) 31 | 32 | sentencePage = PromptPage(prompt_type="sentence") 33 | sentencePage.updated.connect(self.__textChanged) 34 | 35 | self.__prompt = QTextBrowser() 36 | self.__prompt.setPlaceholderText(LangClass.TRANSLATIONS["Generated Prompt"]) 37 | self.__prompt.setAcceptRichText(False) 38 | 39 | promptTabWidget = QTabWidget() 40 | promptTabWidget.addTab(formPage, LangClass.TRANSLATIONS["Form"]) 41 | promptTabWidget.addTab(sentencePage, LangClass.TRANSLATIONS["Sentence"]) 42 | 43 | previewLbl = QLabel(LangClass.TRANSLATIONS["Preview"]) 44 | 45 | copyBtn = QPushButton(LangClass.TRANSLATIONS["Copy"]) 46 | copyBtn.clicked.connect(self.__copy) 47 | 48 | lay = QVBoxLayout() 49 | lay.addWidget(promptLbl) 50 | lay.addWidget(promptTabWidget) 51 | 52 | topWidget = QWidget() 53 | topWidget.setLayout(lay) 54 | 55 | lay = QVBoxLayout() 56 | lay.addWidget(previewLbl) 57 | lay.addWidget(self.__prompt) 58 | lay.addWidget(copyBtn) 59 | 60 | bottomWidget = QWidget() 61 | bottomWidget.setLayout(lay) 62 | 63 | mainSplitter = QSplitter() 64 | mainSplitter.addWidget(topWidget) 65 | mainSplitter.addWidget(bottomWidget) 66 | mainSplitter.setOrientation(Qt.Orientation.Vertical) 67 | mainSplitter.setChildrenCollapsible(False) 68 | mainSplitter.setHandleWidth(2) 69 | mainSplitter.setStyleSheet( 70 | """ 71 | QSplitter::handle:vertical 72 | { 73 | background: #CCC; 74 | height: 1px; 75 | } 76 | """, 77 | ) 78 | 79 | self.setWidget(mainSplitter) 80 | self.setWidgetResizable(True) 81 | 82 | self.setStyleSheet("QScrollArea { border: 0 }") 83 | 84 | def __textChanged(self, prompt_text): 85 | self.__prompt.clear() 86 | self.__prompt.setText(prompt_text) 87 | 88 | def __copy(self): 89 | pyperclip.copy(self.__prompt.toPlainText()) 90 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/promptGroupDirectInputDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import ( 5 | QDialog, 6 | QHBoxLayout, 7 | QLineEdit, 8 | QMessageBox, 9 | QPushButton, 10 | QVBoxLayout, 11 | QWidget, 12 | ) 13 | 14 | from pyqt_openai.lang.translations import LangClass 15 | from pyqt_openai.util.common import getSeparator, is_prompt_group_name_valid 16 | 17 | 18 | class PromptGroupDirectInputDialog(QDialog): 19 | def __init__(self, parent=None): 20 | super().__init__(parent) 21 | self.__initUi() 22 | 23 | def __initUi(self): 24 | self.setWindowTitle(LangClass.TRANSLATIONS["New Prompt"]) 25 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 26 | 27 | self.__name = QLineEdit() 28 | self.__name.setPlaceholderText(LangClass.TRANSLATIONS["Name"]) 29 | self.__name.textChanged.connect( 30 | lambda x: self.__okBtn.setEnabled(x.strip() != ""), 31 | ) 32 | 33 | sep = getSeparator("horizontal") 34 | 35 | self.__okBtn = QPushButton(LangClass.TRANSLATIONS["OK"]) 36 | self.__okBtn.clicked.connect(self.__accept) 37 | 38 | cancelBtn = QPushButton(LangClass.TRANSLATIONS["Cancel"]) 39 | cancelBtn.clicked.connect(self.close) 40 | 41 | hlay = QHBoxLayout() 42 | hlay.addWidget(self.__okBtn) 43 | hlay.addWidget(cancelBtn) 44 | hlay.setAlignment(Qt.AlignmentFlag.AlignRight) 45 | hlay.setContentsMargins(0, 0, 0, 0) 46 | 47 | okCancelWidget = QWidget() 48 | okCancelWidget.setLayout(hlay) 49 | 50 | vlay = QVBoxLayout() 51 | vlay.addWidget(self.__name) 52 | vlay.addWidget(sep) 53 | vlay.addWidget(okCancelWidget) 54 | 55 | self.setLayout(vlay) 56 | 57 | def getPromptGroupName(self) -> str: 58 | return self.__name.text() 59 | 60 | def __accept(self): 61 | f = is_prompt_group_name_valid(self.__name.text()) 62 | if f: 63 | self.accept() 64 | else: 65 | self.__name.setFocus() 66 | QMessageBox.warning( # type: ignore[call-arg] 67 | self, 68 | LangClass.TRANSLATIONS["Warning"], 69 | LangClass.TRANSLATIONS["Prompt name already exists."], 70 | ) 71 | return 72 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/promptGroupExportDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import ( 5 | QCheckBox, 6 | QDialog, 7 | QDialogButtonBox, 8 | QLabel, 9 | QPushButton, 10 | QVBoxLayout, 11 | ) 12 | 13 | from pyqt_openai import FORM_PROMPT_GROUP_SAMPLE, SENTENCE_PROMPT_GROUP_SAMPLE 14 | from pyqt_openai.chat_widget.prompt_gen_widget.promptCsvRightFormSampleDialog import ( 15 | PromptCSVRightFormSampleDialog, 16 | ) 17 | from pyqt_openai.lang.translations import LangClass 18 | from pyqt_openai.util.common import showJsonSample 19 | from pyqt_openai.widgets.checkBoxListWidget import CheckBoxListWidget 20 | from pyqt_openai.widgets.jsonEditor import JSONEditor 21 | 22 | 23 | class PromptGroupExportDialog(QDialog): 24 | def __init__(self, data, prompt_type="form", ext=".json", parent=None): 25 | super().__init__(parent) 26 | self.__initVal(data, prompt_type, ext) 27 | self.__initUi() 28 | 29 | def __initVal(self, data, prompt_type, ext): 30 | self.__data = data 31 | self.__promptType = prompt_type 32 | self.__ext = ext 33 | 34 | def __initUi(self): 35 | self.setWindowTitle(LangClass.TRANSLATIONS["Export Prompt Group"]) 36 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 37 | 38 | btnText = LangClass.TRANSLATIONS["Preview of the JSON format to be created after export"] if self.__ext == ".json" else LangClass.TRANSLATIONS["Preview of the CSV format to be created after export"] 39 | btn = QPushButton(btnText) 40 | 41 | if self.__ext == ".json": 42 | btn.clicked.connect(self.__showJsonSample) 43 | self.__jsonSampleWidget = JSONEditor() 44 | elif self.__ext == ".csv": 45 | btn.clicked.connect(self.__showCSVSample) 46 | 47 | allCheckBox = QCheckBox(LangClass.TRANSLATIONS["Select All"]) 48 | self.__listWidget = CheckBoxListWidget() 49 | self.__listWidget.addItems([d["name"] for d in self.__data]) 50 | self.__listWidget.checkedSignal.connect(self.__toggledBtn) 51 | allCheckBox.stateChanged.connect(self.__listWidget.toggleState) 52 | 53 | self.__buttonBox = QDialogButtonBox( 54 | QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, 55 | ) 56 | self.__buttonBox.accepted.connect(self.accept) 57 | self.__buttonBox.rejected.connect(self.reject) 58 | 59 | lay = QVBoxLayout() 60 | lay.addWidget( 61 | QLabel(LangClass.TRANSLATIONS["Select the prompts you want to export."]), 62 | ) 63 | lay.addWidget(allCheckBox) 64 | lay.addWidget(self.__listWidget) 65 | lay.addWidget(btn) 66 | lay.addWidget(self.__buttonBox) 67 | self.setLayout(lay) 68 | 69 | self.setLayout(lay) 70 | 71 | allCheckBox.setChecked(True) 72 | 73 | def __toggledBtn(self): 74 | self.__buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled( 75 | len(self.__listWidget.getCheckedRows()) > 0, 76 | ) 77 | 78 | def __showJsonSample(self): 79 | json_sample = ( 80 | FORM_PROMPT_GROUP_SAMPLE 81 | if self.__promptType == "form" 82 | else SENTENCE_PROMPT_GROUP_SAMPLE 83 | ) 84 | showJsonSample(self.__jsonSampleWidget, json_sample) 85 | 86 | def __showCSVSample(self): 87 | dialog = PromptCSVRightFormSampleDialog(self) 88 | dialog.exec() 89 | 90 | def getSelected(self): 91 | """Get selected prompt group names. 92 | The data is used to export the selected prompt groups. 93 | This function is giving names instead of ids because the name field is unique anyway. 94 | """ 95 | names = [ 96 | self.__listWidget.item(r).text() for r in self.__listWidget.getCheckedRows() 97 | ] 98 | result = [d for d in self.__data if d["name"] in names] 99 | return result 100 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/prompt_gen_widget/promptPage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Signal 4 | from qtpy.QtWidgets import ( 5 | QSplitter, 6 | QVBoxLayout, 7 | QWidget, 8 | ) 9 | 10 | from pyqt_openai.chat_widget.prompt_gen_widget.promptGroupList import PromptGroupList 11 | from pyqt_openai.chat_widget.prompt_gen_widget.promptTable import PromptTable 12 | from pyqt_openai.globals import DB 13 | 14 | 15 | class PromptPage(QWidget): 16 | updated = Signal(str) 17 | 18 | def __init__(self, prompt_type="form", parent=None): 19 | super().__init__(parent) 20 | self.__initVal(prompt_type) 21 | self.__initUi() 22 | 23 | def __initVal(self, prompt_type): 24 | self.prompt_type = prompt_type 25 | self.__groups = DB.selectPromptGroup(prompt_type=self.prompt_type) 26 | 27 | def __initUi(self): 28 | leftWidget = PromptGroupList(prompt_type=self.prompt_type) 29 | leftWidget.added.connect(self.add) 30 | leftWidget.deleted.connect(self.delete) 31 | 32 | leftWidget.currentRowChanged.connect(self.__showEntries) 33 | 34 | self.__table = PromptTable() 35 | if len(self.__groups) > 0: 36 | leftWidget.list.setCurrentRow(0) 37 | self.__table.showEntries(self.__groups[0].id) 38 | self.__table.updated.connect(self.updated) 39 | 40 | mainWidget = QSplitter() 41 | mainWidget.addWidget(leftWidget) 42 | mainWidget.addWidget(self.__table) 43 | mainWidget.setChildrenCollapsible(False) 44 | mainWidget.setSizes([300, 700]) 45 | 46 | lay = QVBoxLayout() 47 | lay.addWidget(mainWidget) 48 | 49 | self.setLayout(lay) 50 | 51 | def add(self, id): 52 | self.__table.showEntries(id) 53 | 54 | def delete(self, id): 55 | if self.__table.getId() == id or len(DB.selectPromptGroup(prompt_type=self.prompt_type)) == 0: 56 | self.__table.setNothingRightNow() 57 | 58 | def __showEntries(self, id): 59 | self.__table.showEntries(id) 60 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/right_sidebar/chatRightSideBarWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from functools import partial 4 | 5 | from qtpy.QtCore import Signal 6 | from qtpy.QtWidgets import QGridLayout, QMessageBox, QScrollArea, QTabWidget, QWidget 7 | 8 | from pyqt_openai.chat_widget.right_sidebar.llama_widget.llamaPage import LlamaPage 9 | from pyqt_openai.chat_widget.right_sidebar.usingAPIPage import UsingAPIPage 10 | from pyqt_openai.chat_widget.right_sidebar.usingG4FPage import UsingG4FPage 11 | from pyqt_openai.config_loader import CONFIG_MANAGER 12 | from pyqt_openai.globals import LLAMAINDEX_WRAPPER 13 | from pyqt_openai.lang.translations import LangClass 14 | 15 | 16 | class ChatRightSideBarWidget(QScrollArea): 17 | onTabChanged = Signal(int) 18 | onToggleJSON = Signal(bool) 19 | 20 | def __init__(self, parent=None): 21 | super().__init__(parent) 22 | self.__initVal() 23 | self.__initUi() 24 | 25 | def __initVal(self): 26 | self.__cur_idx = CONFIG_MANAGER.get_general_property("TAB_IDX") 27 | self.__use_llama_index = CONFIG_MANAGER.get_general_property("use_llama_index") 28 | self.__llama_index_directory = CONFIG_MANAGER.get_general_property( 29 | "llama_index_directory", 30 | ) 31 | 32 | def __initUi(self): 33 | tabWidget = QTabWidget() 34 | tabWidget.currentChanged.connect(self.onTabChanged.emit) 35 | 36 | usingG4FPage = UsingG4FPage() 37 | usingAPIPage = UsingAPIPage() 38 | self.__llamaPage = LlamaPage() 39 | self.__llamaPage.onDirectorySelected.connect(self.__onDirectorySelected) 40 | 41 | # TODO LANGUAGE 42 | tabWidget.addTab(usingG4FPage, "Using G4F (Free)") 43 | tabWidget.addTab(usingAPIPage, "Using API") 44 | tabWidget.addTab(self.__llamaPage, "LlamaIndex") 45 | tabWidget.currentChanged.connect(self.__tabChanged) 46 | tabWidget.setTabEnabled(2, self.__use_llama_index) 47 | tabWidget.setCurrentIndex(self.__cur_idx) 48 | 49 | partial_func = partial(tabWidget.setTabEnabled, 2) 50 | usingAPIPage.onToggleLlama.connect(lambda x: partial_func(x)) 51 | usingAPIPage.onToggleJSON.connect(self.onToggleJSON) 52 | 53 | lay = QGridLayout() 54 | lay.addWidget(tabWidget) 55 | 56 | mainWidget = QWidget() 57 | mainWidget.setLayout(lay) 58 | 59 | self.setWidget(mainWidget) 60 | self.setWidgetResizable(True) 61 | 62 | self.setStyleSheet("QScrollArea { border: 0 }") 63 | 64 | def __tabChanged(self, idx): 65 | self.__cur_idx = idx 66 | CONFIG_MANAGER.set_general_property("TAB_IDX", self.__cur_idx) 67 | 68 | def __onDirectorySelected(self, selected_dirname): 69 | self.__llama_index_directory = selected_dirname 70 | CONFIG_MANAGER.set_general_property("llama_index_directory", selected_dirname) 71 | try: 72 | LLAMAINDEX_WRAPPER.set_directory(selected_dirname) 73 | except Exception as e: 74 | QMessageBox.critical(self, LangClass.TRANSLATIONS["Error"], str(e)) 75 | 76 | 77 | def currentTabIdx(self): 78 | return self.__cur_idx 79 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/right_sidebar/llama_widget/filesWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from qtpy.QtCore import Signal 6 | from qtpy.QtWidgets import ( 7 | QFileDialog, 8 | QHBoxLayout, 9 | QLabel, 10 | QListWidget, 11 | QPushButton, 12 | QSizePolicy, 13 | QSpacerItem, 14 | QVBoxLayout, 15 | QWidget, 16 | ) 17 | 18 | from pyqt_openai import QFILEDIALOG_DEFAULT_DIRECTORY 19 | from pyqt_openai.config_loader import CONFIG_MANAGER 20 | from pyqt_openai.lang.translations import LangClass 21 | from pyqt_openai.util.common import getSeparator 22 | 23 | 24 | class FilesWidget(QWidget): 25 | itemUpdate = Signal(bool) 26 | onDirectorySelected = Signal() 27 | clicked = Signal(str) 28 | 29 | def __init__(self, parent=None): 30 | super().__init__(parent) 31 | self.__initVal() 32 | self.__initUi() 33 | 34 | def __initVal(self): 35 | self.__directory_label_prefix = LangClass.TRANSLATIONS["Directory"] 36 | self.__current_directory_name = "" 37 | self.__extension = CONFIG_MANAGER.get_general_property("llama_index_supported_formats") 38 | 39 | def __initUi(self): 40 | lbl = QLabel(LangClass.TRANSLATIONS["Files"]) 41 | setDirBtn = QPushButton(LangClass.TRANSLATIONS["Set Directory"]) 42 | setDirBtn.clicked.connect(self.setDirectory) 43 | self.__dirLbl = QLabel(self.__directory_label_prefix) 44 | 45 | lay = QHBoxLayout() 46 | lay.addWidget(lbl) 47 | lay.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Policy.MinimumExpanding)) 48 | lay.addWidget(setDirBtn) 49 | lay.setContentsMargins(0, 0, 0, 0) 50 | 51 | topWidget = QWidget() 52 | topWidget.setLayout(lay) 53 | 54 | self.__listWidget = QListWidget() 55 | self.__listWidget.itemClicked.connect(self.__sendDirectory) 56 | 57 | sep = getSeparator("horizontal") 58 | 59 | lay = QVBoxLayout() 60 | lay.addWidget(topWidget) 61 | lay.addWidget(sep) 62 | lay.addWidget(self.__dirLbl) 63 | lay.addWidget(self.__listWidget) 64 | lay.setContentsMargins(0, 0, 0, 0) 65 | self.setLayout(lay) 66 | 67 | def setExtension(self, ext): 68 | # Set extension 69 | self.__extension = ext 70 | CONFIG_MANAGER.set_general_property("llama_index_supported_formats", ext) 71 | 72 | # Refresh list based on new extension 73 | self.__listWidget.clear() 74 | self.setDirectory(self.__current_directory_name, called_from_btn=False) 75 | 76 | def setDirectory(self, directory=None, called_from_btn=True): 77 | try: 78 | if called_from_btn: 79 | if not directory: 80 | directory = QFileDialog.getExistingDirectory( 81 | self, 82 | LangClass.TRANSLATIONS["Select Directory"], 83 | QFILEDIALOG_DEFAULT_DIRECTORY, 84 | QFileDialog.Option.ShowDirsOnly, 85 | ) 86 | if directory: 87 | self.__listWidget.clear() 88 | filenames = list( 89 | filter( 90 | lambda x: os.path.splitext(x)[-1] in self.__extension, 91 | os.listdir(directory), 92 | ), 93 | ) 94 | self.__listWidget.addItems(filenames) 95 | self.itemUpdate.emit(len(filenames) > 0) 96 | self.__current_directory_name = directory 97 | self.__dirLbl.setText(self.__current_directory_name.split("/")[-1]) 98 | 99 | self.__listWidget.setCurrentRow(0) 100 | # activate event as clicking first item (because this selects the first item anyway) 101 | self.clicked.emit( 102 | os.path.join( 103 | self.__current_directory_name, self.__listWidget.currentItem().text(), 104 | ), 105 | ) 106 | self.onDirectorySelected.emit() 107 | except Exception as e: 108 | print(e) 109 | 110 | def getDirectory(self): 111 | return self.__current_directory_name 112 | 113 | def __sendDirectory(self, item): 114 | self.clicked.emit(os.path.join(self.__current_directory_name, item.text())) 115 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/right_sidebar/llama_widget/llamaPage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt, Signal 4 | from qtpy.QtGui import QFont 5 | from qtpy.QtWidgets import QLabel, QTextBrowser, QVBoxLayout, QWidget 6 | 7 | from pyqt_openai import SMALL_LABEL_PARAM 8 | from pyqt_openai.chat_widget.right_sidebar.llama_widget.filesWidget import FilesWidget 9 | from pyqt_openai.chat_widget.right_sidebar.llama_widget.supportedFileFormatsWidget import ( 10 | SupportedFileFormatsWidget, 11 | ) 12 | from pyqt_openai.config_loader import CONFIG_MANAGER 13 | from pyqt_openai.lang.translations import LangClass 14 | 15 | 16 | class LlamaPage(QWidget): 17 | onDirectorySelected = Signal(str) 18 | 19 | def __init__(self, parent=None): 20 | super().__init__(parent) 21 | self.__initUi() 22 | 23 | def __initUi(self): 24 | self.__apiCheckPreviewLbl = QLabel() 25 | self.__apiCheckPreviewLbl.setFont(QFont(*SMALL_LABEL_PARAM)) 26 | 27 | self.__filesWidget = FilesWidget() 28 | self.__filesWidget.clicked.connect(self.__setTextInBrowser) 29 | self.__filesWidget.onDirectorySelected.connect(self.__onDirectorySelected) 30 | 31 | self.__supportedFileFormatsWidget = SupportedFileFormatsWidget() 32 | self.__supportedFileFormatsWidget.checkedSignal.connect(self.__formatCheckedSignal) 33 | 34 | self.__txtBrowser = QTextBrowser() 35 | self.__txtBrowser.setPlaceholderText( 36 | LangClass.TRANSLATIONS[ 37 | "This text browser shows selected file's content in the list." 38 | ], 39 | ) 40 | self.__txtBrowser.setMaximumHeight(150) 41 | 42 | lay = QVBoxLayout() 43 | lay.addWidget(self.__supportedFileFormatsWidget) 44 | lay.addWidget(self.__filesWidget) 45 | lay.addWidget(self.__txtBrowser) 46 | lay.setAlignment(Qt.AlignmentFlag.AlignTop) 47 | 48 | self.setLayout(lay) 49 | 50 | self.setDirectory() 51 | 52 | def __onDirectorySelected(self): 53 | selected_dirname = self.__filesWidget.getDirectory() 54 | self.onDirectorySelected.emit(selected_dirname) 55 | 56 | def __setTextInBrowser(self, file): 57 | try: 58 | with open(file, encoding="utf-8") as f: 59 | self.__txtBrowser.setText(f.read()) 60 | except UnicodeDecodeError as e: 61 | self.__txtBrowser.setText("Some files like Excel files cannot be previewed.") 62 | except Exception as e: 63 | print(e) 64 | 65 | def setDirectory(self): 66 | directory = CONFIG_MANAGER.get_general_property("llama_index_directory") 67 | self.__filesWidget.setDirectory(directory, called_from_btn=False) 68 | 69 | def __formatCheckedSignal(self, ext): 70 | self.__filesWidget.setExtension(ext) 71 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/right_sidebar/llama_widget/supportedFileFormatsWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt, Signal 4 | from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget 5 | 6 | from pyqt_openai import LLAMA_INDEX_DEFAULT_ALL_SUPPORTED_FORMATS_LIST 7 | from pyqt_openai.config_loader import CONFIG_MANAGER 8 | from pyqt_openai.widgets.checkBoxListWidget import CheckBoxListWidget 9 | 10 | 11 | class SupportedFileFormatsWidget(QWidget): 12 | checkedSignal = Signal(list) 13 | 14 | def __init__(self, parent=None): 15 | super().__init__(parent) 16 | self.__initUi() 17 | 18 | def __initUi(self): 19 | self.__listWidget = CheckBoxListWidget() 20 | self.__listWidget.checkedSignal.connect(self.__sendCheckedSignal) 21 | 22 | all_supported_format_lst = LLAMA_INDEX_DEFAULT_ALL_SUPPORTED_FORMATS_LIST 23 | self.__listWidget.addItems(all_supported_format_lst) 24 | 25 | current_supported_format_lst = CONFIG_MANAGER.get_general_property("llama_index_supported_formats") 26 | for i in range(self.__listWidget.count()): 27 | supported_format = self.__listWidget.item(i).text() 28 | if supported_format in current_supported_format_lst: 29 | self.__listWidget.item(i).setCheckState(Qt.CheckState.Checked) 30 | 31 | lay = QVBoxLayout() 32 | # TODO LANGUAGE 33 | lay.addWidget(QLabel("Supported File Formats")) 34 | lay.addWidget(self.__listWidget) 35 | lay.setContentsMargins(0, 0, 0, 0) 36 | self.setLayout(lay) 37 | 38 | def __sendCheckedSignal(self, r_idx, state): 39 | self.checkedSignal.emit(self.__listWidget.getCheckedItemsText()) 40 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/right_sidebar/modelSearchBar.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import QCompleter, QLineEdit 5 | 6 | from pyqt_openai.util.common import get_chat_model 7 | 8 | 9 | class ModelSearchBar(QLineEdit): 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | all_models = get_chat_model() 13 | # TODO LANGAUGE 14 | self.setPlaceholderText("Start typing a model name...") 15 | 16 | completer = QCompleter(all_models) 17 | completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) 18 | self.setCompleter(completer) 19 | -------------------------------------------------------------------------------- /pyqt_openai/chat_widget/right_sidebar/usingG4FPage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt, Signal 4 | from qtpy.QtWidgets import ( 5 | QCheckBox, 6 | QComboBox, 7 | QFormLayout, 8 | QSizePolicy, 9 | QTextBrowser, 10 | QWidget, 11 | ) 12 | 13 | from pyqt_openai import G4F_PROVIDER_DEFAULT 14 | from pyqt_openai.config_loader import CONFIG_MANAGER 15 | from pyqt_openai.lang.translations import LangClass 16 | from pyqt_openai.util.common import ( 17 | getSeparator, 18 | get_chat_model, 19 | get_g4f_models, 20 | get_g4f_models_by_provider, 21 | get_g4f_providers, 22 | ) 23 | 24 | 25 | class UsingG4FPage(QWidget): 26 | onToggleLlama = Signal(bool) 27 | onToggleJSON = Signal(bool) 28 | 29 | def __init__(self, parent=None): 30 | super().__init__(parent) 31 | self.__initVal() 32 | self.__initUi() 33 | 34 | def __initVal(self): 35 | self.__stream = CONFIG_MANAGER.get_general_property("stream") 36 | self.__model = CONFIG_MANAGER.get_general_property("g4f_model") 37 | self.__provider = CONFIG_MANAGER.get_general_property("provider") 38 | 39 | def __initUi(self): 40 | manualBrowser = QTextBrowser() 41 | manualBrowser.setOpenExternalLinks(True) 42 | manualBrowser.setOpenLinks(True) 43 | 44 | # TODO LANGUAGE 45 | manualBrowser.setHtml( 46 | """ 47 |

Using GPT4Free (Free)

48 |

Description

49 |

- Responses may often be slow or incomplete.

50 |

- The response server may be unstable.

51 | """, 52 | ) 53 | manualBrowser.setSizePolicy( 54 | QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred, 55 | ) 56 | 57 | self.__modelCmbBox = QComboBox() 58 | self.__modelCmbBox.addItems(get_chat_model(is_g4f=True)) 59 | self.__modelCmbBox.setCurrentText(self.__model) 60 | self.__modelCmbBox.currentTextChanged.connect(self.__modelChanged) 61 | 62 | streamChkBox = QCheckBox() 63 | streamChkBox.setChecked(self.__stream) 64 | streamChkBox.toggled.connect(self.__streamChecked) 65 | streamChkBox.setText(LangClass.TRANSLATIONS["Stream"]) 66 | 67 | providerCmbBox = QComboBox() 68 | providerCmbBox.addItems(get_g4f_providers(including_auto=True)) 69 | providerCmbBox.setCurrentText(self.__provider) 70 | providerCmbBox.currentTextChanged.connect(self.__providerChanged) 71 | 72 | # TODO LANGUAGE 73 | # TODO NEEDS ADDITIONAL DESCRIPTION 74 | g4f_use_chat_historyChkBox = QCheckBox("Use chat history") 75 | g4f_use_chat_historyChkBox.setChecked( 76 | CONFIG_MANAGER.get_general_property("g4f_use_chat_history"), 77 | ) 78 | g4f_use_chat_historyChkBox.toggled.connect(self.__saveChatHistory) 79 | 80 | lay = QFormLayout() 81 | lay.addRow(manualBrowser) 82 | lay.addRow(getSeparator("horizontal")) 83 | lay.addRow("Model", self.__modelCmbBox) 84 | lay.addRow("Provider", providerCmbBox) 85 | lay.addRow(streamChkBox) 86 | lay.addRow(g4f_use_chat_historyChkBox) 87 | lay.setAlignment(Qt.AlignmentFlag.AlignTop) 88 | 89 | self.setLayout(lay) 90 | 91 | def __modelChanged(self, v): 92 | self.__model = v 93 | CONFIG_MANAGER.set_general_property("g4f_model", v) 94 | 95 | def __streamChecked(self, f): 96 | self.__stream = f 97 | CONFIG_MANAGER.set_general_property("stream", f) 98 | 99 | def __providerChanged(self, v): 100 | self.__modelCmbBox.clear() 101 | CONFIG_MANAGER.set_general_property("provider", v) 102 | if v == G4F_PROVIDER_DEFAULT: 103 | self.__modelCmbBox.addItems(get_g4f_models()) 104 | else: 105 | self.__modelCmbBox.addItems(get_g4f_models_by_provider(v)) 106 | 107 | def __saveChatHistory(self, f): 108 | CONFIG_MANAGER.set_general_property("g4f_use_chat_history", f) 109 | -------------------------------------------------------------------------------- /pyqt_openai/dalle_widget/dalleHome.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtGui import QFont 5 | from qtpy.QtWidgets import QLabel, QScrollArea, QVBoxLayout, QWidget 6 | 7 | from pyqt_openai import CONTEXT_DELIMITER, LARGE_LABEL_PARAM, MEDIUM_LABEL_PARAM 8 | 9 | 10 | class DallEHome(QScrollArea): 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | self.__initUi() 14 | 15 | def __initUi(self): 16 | title = QLabel("Welcome to DALL-E Page !", self) 17 | title.setFont(QFont(*LARGE_LABEL_PARAM)) 18 | title.setAlignment(Qt.AlignmentFlag.AlignCenter) 19 | 20 | description = QLabel("Generate images with DALL-E." + CONTEXT_DELIMITER) 21 | 22 | description.setFont(QFont(*MEDIUM_LABEL_PARAM)) 23 | description.setAlignment(Qt.AlignmentFlag.AlignCenter) 24 | 25 | lay = QVBoxLayout() 26 | lay.addWidget(title) 27 | lay.addWidget(description) 28 | lay.setAlignment(Qt.AlignmentFlag.AlignCenter) 29 | self.setLayout(lay) 30 | 31 | mainWidget = QWidget() 32 | mainWidget.setLayout(lay) 33 | self.setWidget(mainWidget) 34 | self.setWidgetResizable(True) 35 | -------------------------------------------------------------------------------- /pyqt_openai/dalle_widget/dalleMainWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pyqt_openai.config_loader import CONFIG_MANAGER 4 | from pyqt_openai.dalle_widget.dalleHome import DallEHome 5 | from pyqt_openai.dalle_widget.dalleRightSideBar import DallERightSideBarWidget 6 | from pyqt_openai.widgets.imageMainWidget import ImageMainWidget 7 | 8 | 9 | class DallEMainWidget(ImageMainWidget): 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | self.__initUi() 13 | 14 | def __initUi(self): 15 | self._homePage = DallEHome() 16 | self._rightSideBarWidget = DallERightSideBarWidget() 17 | 18 | self._setHomeWidget(self._homePage) 19 | self._setRightSideBarWidget(self._rightSideBarWidget) 20 | self._completeUi() 21 | 22 | def toggleHistory(self, f): 23 | super().toggleHistory(f) 24 | CONFIG_MANAGER.set_dalle_property("show_history", f) 25 | 26 | def toggleSetting(self, f): 27 | super().toggleSetting(f) 28 | CONFIG_MANAGER.set_dalle_property("show_setting", f) 29 | -------------------------------------------------------------------------------- /pyqt_openai/dalle_widget/dalleThread.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import base64 4 | 5 | from qtpy.QtCore import QThread, Signal 6 | 7 | from pyqt_openai.globals import OPENAI_CLIENT 8 | from pyqt_openai.models import ImagePromptContainer 9 | from pyqt_openai.util.common import generate_random_prompt 10 | 11 | 12 | class DallEThread(QThread): 13 | replyGenerated = Signal(ImagePromptContainer) 14 | errorGenerated = Signal(str) 15 | allReplyGenerated = Signal() 16 | 17 | def __init__( 18 | self, input_args, number_of_images, randomizing_prompt_source_arr=None, 19 | ): 20 | super().__init__() 21 | self.__input_args = input_args 22 | self.__stop = False 23 | 24 | self.__randomizing_prompt_source_arr = randomizing_prompt_source_arr 25 | self.__number_of_images = number_of_images 26 | 27 | def stop(self): 28 | self.__stop = True 29 | 30 | def run(self): 31 | try: 32 | for _ in range(self.__number_of_images): 33 | if self.__stop: 34 | break 35 | if self.__randomizing_prompt_source_arr is not None: 36 | self.__input_args["prompt"] = generate_random_prompt( 37 | self.__randomizing_prompt_source_arr, 38 | ) 39 | response = OPENAI_CLIENT.images.generate(**self.__input_args) 40 | container = ImagePromptContainer(**self.__input_args) 41 | for _ in response.data: 42 | image_data = base64.b64decode(_.b64_json) 43 | container.data = image_data 44 | container.revised_prompt = _.revised_prompt 45 | container.width = self.__input_args["size"].split("x")[0] 46 | container.height = self.__input_args["size"].split("x")[1] 47 | self.replyGenerated.emit(container) 48 | self.allReplyGenerated.emit() 49 | except Exception as e: 50 | self.errorGenerated.emit(str(e)) 51 | -------------------------------------------------------------------------------- /pyqt_openai/doNotAskAgainDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt, Signal 4 | from qtpy.QtWidgets import QCheckBox, QDialog, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QSpacerItem, QVBoxLayout, QWidget 5 | 6 | from pyqt_openai.lang.translations import LangClass 7 | from pyqt_openai.util.common import getSeparator 8 | 9 | 10 | class DoNotAskAgainDialog(QDialog): 11 | doNotAskAgainChanged = Signal(bool) 12 | 13 | def __init__( 14 | self, 15 | do_not_ask_again: bool = False, 16 | parent: QWidget | None = None, 17 | ) -> None: 18 | super().__init__(parent) 19 | do_not_ask_again_message: str = LangClass.TRANSLATIONS[ 20 | "Would you like to exit the application? If you won't, it will be running in the background." 21 | ] 22 | do_not_ask_again_checkbox_message = LangClass.TRANSLATIONS["Do not ask again"] 23 | self.__initVal( 24 | do_not_ask_again, 25 | do_not_ask_again_message, 26 | do_not_ask_again_checkbox_message, 27 | ) 28 | self.__initUi() 29 | 30 | def __initVal( 31 | self, 32 | do_not_ask_again, 33 | do_not_ask_again_message, 34 | do_not_ask_again_checkbox_message, 35 | ): 36 | self.__is_cancel = False 37 | self.__do_not_ask_again = do_not_ask_again 38 | self.__do_not_ask_again_message = do_not_ask_again_message 39 | self.__do_not_ask_again_checkbox_message = do_not_ask_again_checkbox_message 40 | 41 | def __initUi(self): 42 | self.setWindowTitle(LangClass.TRANSLATIONS["Exit"]) 43 | self.setModal(True) 44 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 45 | 46 | self.label: QLabel = QLabel(self.__do_not_ask_again_message) 47 | self.label.setAlignment(Qt.AlignmentFlag.AlignCenter) 48 | 49 | self.yesButton: QPushButton = QPushButton(LangClass.TRANSLATIONS["Yes"]) 50 | self.yesButton.clicked.connect(self.accept) 51 | 52 | self.noButton: QPushButton = QPushButton(LangClass.TRANSLATIONS["No"]) 53 | self.noButton.clicked.connect(self.reject) 54 | 55 | self.cancelButton: QPushButton = QPushButton(LangClass.TRANSLATIONS["Cancel"]) 56 | self.cancelButton.clicked.connect(self.__cancel) 57 | 58 | self.doNotAskAgainCheckBox: QCheckBox = QCheckBox( 59 | self.__do_not_ask_again_checkbox_message, 60 | ) 61 | self.doNotAskAgainCheckBox.setChecked(self.__do_not_ask_again) 62 | self.doNotAskAgainCheckBox.stateChanged.connect(self.__onCheckBoxStateChanged) 63 | 64 | sep = getSeparator("horizontal") 65 | 66 | lay = QHBoxLayout() 67 | lay.addWidget(self.doNotAskAgainCheckBox) 68 | lay.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Policy.MinimumExpanding)) 69 | lay.addWidget(self.yesButton) 70 | lay.addWidget(self.noButton) 71 | lay.addWidget(self.cancelButton) 72 | lay.setAlignment(Qt.AlignmentFlag.AlignRight) 73 | lay.setContentsMargins(0, 0, 0, 0) 74 | btnWidget = QWidget() 75 | btnWidget.setLayout(lay) 76 | 77 | lay = QVBoxLayout() 78 | lay.addWidget(self.label) 79 | lay.addWidget(sep) 80 | lay.addWidget(btnWidget) 81 | 82 | self.setLayout(lay) 83 | 84 | def __cancel(self): 85 | self.__is_cancel = True 86 | self.reject() 87 | 88 | def __onCheckBoxStateChanged( 89 | self, 90 | state: int, 91 | ) -> None: 92 | self.__do_not_ask_again: bool = state == 2 93 | self.doNotAskAgainChanged.emit(self.__do_not_ask_again) 94 | 95 | def isCancel(self): 96 | return self.__is_cancel 97 | -------------------------------------------------------------------------------- /pyqt_openai/g4f_image_widget/g4fImageHome.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtGui import QFont 5 | from qtpy.QtWidgets import QLabel, QScrollArea, QVBoxLayout, QWidget 6 | 7 | from pyqt_openai import CONTEXT_DELIMITER, LARGE_LABEL_PARAM, MEDIUM_LABEL_PARAM 8 | 9 | 10 | class G4FImageHome(QScrollArea): 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | self.__initUi() 14 | 15 | def __initUi(self): 16 | # TODO LANGUAGE 17 | title = QLabel("Welcome to GPT4Free\n" + "Image Generation Page !", self) 18 | title.setFont(QFont(*LARGE_LABEL_PARAM)) 19 | title.setAlignment(Qt.AlignmentFlag.AlignCenter) 20 | 21 | description = QLabel( 22 | "Generate images for free with the power of G4F." + CONTEXT_DELIMITER, 23 | ) 24 | 25 | description.setFont(QFont(*MEDIUM_LABEL_PARAM)) 26 | description.setAlignment(Qt.AlignmentFlag.AlignCenter) 27 | 28 | # TODO v2.x.0 "how does this work?" or "What is GPT4Free?" link (maybe) 29 | 30 | lay = QVBoxLayout() 31 | lay.addWidget(title) 32 | lay.addWidget(description) 33 | lay.setAlignment(Qt.AlignmentFlag.AlignCenter) 34 | self.setLayout(lay) 35 | 36 | mainWidget = QWidget() 37 | mainWidget.setLayout(lay) 38 | self.setWidget(mainWidget) 39 | self.setWidgetResizable(True) 40 | -------------------------------------------------------------------------------- /pyqt_openai/g4f_image_widget/g4fImageMainWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pyqt_openai.config_loader import CONFIG_MANAGER 4 | from pyqt_openai.g4f_image_widget.g4fImageHome import G4FImageHome 5 | from pyqt_openai.g4f_image_widget.g4fImageRightSideBar import G4FImageRightSideBarWidget 6 | from pyqt_openai.widgets.imageMainWidget import ImageMainWidget 7 | 8 | 9 | class G4FImageMainWidget(ImageMainWidget): 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | self.__initUi() 13 | 14 | def __initUi(self): 15 | self._homePage = G4FImageHome() 16 | self._rightSideBarWidget = G4FImageRightSideBarWidget() 17 | 18 | self._setHomeWidget(self._homePage) 19 | self._setRightSideBarWidget(self._rightSideBarWidget) 20 | self._completeUi() 21 | 22 | def toggleHistory(self, f): 23 | super().toggleHistory(f) 24 | CONFIG_MANAGER.set_g4f_image_property("show_history", f) 25 | 26 | def toggleSetting(self, f): 27 | super().toggleSetting(f) 28 | CONFIG_MANAGER.set_g4f_image_property("show_setting", f) 29 | -------------------------------------------------------------------------------- /pyqt_openai/g4f_image_widget/g4fImageThread.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pyqt_openai import G4F_PROVIDER_DEFAULT 4 | from pyqt_openai.globals import G4F_CLIENT 5 | from pyqt_openai.models import ImagePromptContainer 6 | from pyqt_openai.util.common import generate_random_prompt 7 | from pyqt_openai.util.replicate import download_image_as_base64 8 | from qtpy.QtCore import QThread, Signal 9 | 10 | 11 | class G4FImageThread(QThread): 12 | replyGenerated = Signal(ImagePromptContainer) 13 | errorGenerated = Signal(str) 14 | allReplyGenerated = Signal() 15 | 16 | def __init__( 17 | self, input_args, number_of_images, randomizing_prompt_source_arr=None 18 | ): 19 | super().__init__() 20 | self.__input_args = input_args 21 | self.__stop = False 22 | 23 | self.__randomizing_prompt_source_arr = randomizing_prompt_source_arr 24 | 25 | self.__number_of_images = number_of_images 26 | 27 | def stop(self): 28 | self.__stop = True 29 | 30 | def run(self): 31 | # try: 32 | if self.__input_args["provider"] == G4F_PROVIDER_DEFAULT: 33 | del self.__input_args["provider"] 34 | 35 | for _ in range(self.__number_of_images): 36 | if self.__stop: 37 | break 38 | if self.__randomizing_prompt_source_arr is not None: 39 | self.__input_args["prompt"] = generate_random_prompt( 40 | self.__randomizing_prompt_source_arr 41 | ) 42 | response = G4F_CLIENT.images.generate( 43 | **self.__input_args 44 | ) 45 | arg = { 46 | **self.__input_args, 47 | "provider": response.provider, 48 | "data": download_image_as_base64(response.data[0].url), 49 | } 50 | 51 | result = ImagePromptContainer(**arg) 52 | self.replyGenerated.emit(result) 53 | self.allReplyGenerated.emit() 54 | # except Exception as e: 55 | # self.errorGenerated.emit(str(e)) -------------------------------------------------------------------------------- /pyqt_openai/globals.py: -------------------------------------------------------------------------------- 1 | """This is the file that contains the global variables that are used, or possibly used, throughout the application.""" 2 | from __future__ import annotations 3 | 4 | from g4f.client import Client 5 | from openai import OpenAI 6 | 7 | from pyqt_openai.sqlite import SqliteDatabase 8 | from pyqt_openai.util.llamaindex import LlamaIndexWrapper 9 | from pyqt_openai.util.replicate import ReplicateWrapper 10 | 11 | DB = SqliteDatabase() 12 | 13 | LLAMAINDEX_WRAPPER = LlamaIndexWrapper() 14 | 15 | G4F_CLIENT = Client() 16 | 17 | # For Whisper 18 | OPENAI_CLIENT = OpenAI(api_key="") 19 | 20 | REPLICATE_CLIENT = ReplicateWrapper(api_key="") 21 | -------------------------------------------------------------------------------- /pyqt_openai/ico/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pyqt_openai/ico/case.svg: -------------------------------------------------------------------------------- 1 | 2 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 3 | 4 | -------------------------------------------------------------------------------- /pyqt_openai/ico/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/customize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pyqt_openai/ico/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pyqt_openai/ico/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/favorite_no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pyqt_openai/ico/favorite_yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pyqt_openai/ico/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pyqt_openai/ico/focus_mode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pyqt_openai/ico/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /pyqt_openai/ico/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/history.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/import.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /pyqt_openai/ico/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pyqt_openai/ico/kofi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjg30737/pyqt-openai/c4565d3beddb95a39a7d66d768dcc17c9be56c62/pyqt_openai/ico/kofi.png -------------------------------------------------------------------------------- /pyqt_openai/ico/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /pyqt_openai/ico/openai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjg30737/pyqt-openai/c4565d3beddb95a39a7d66d768dcc17c9be56c62/pyqt_openai/ico/openai.png -------------------------------------------------------------------------------- /pyqt_openai/ico/openai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/patreon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjg30737/pyqt-openai/c4565d3beddb95a39a7d66d768dcc17c9be56c62/pyqt_openai/ico/paypal.png -------------------------------------------------------------------------------- /pyqt_openai/ico/prev.svg: -------------------------------------------------------------------------------- 1 | 2 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 3 | 4 | -------------------------------------------------------------------------------- /pyqt_openai/ico/prompt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/question.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pyqt_openai/ico/realtime_api.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pyqt_openai/ico/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /pyqt_openai/ico/regex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /pyqt_openai/ico/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic_fluent_send_28_filled 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pyqt_openai/ico/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/shortcut.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/sidebar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pyqt_openai/ico/speaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 13 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pyqt_openai/ico/stackontop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /pyqt_openai/ico/update.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjg30737/pyqt-openai/c4565d3beddb95a39a7d66d768dcc17c9be56c62/pyqt_openai/ico/user.png -------------------------------------------------------------------------------- /pyqt_openai/ico/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/vertical_three_dots.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyqt_openai/ico/word.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by potrace 1.15, written by Peter Selinger 2001-2017 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pyqt_openai/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjg30737/pyqt-openai/c4565d3beddb95a39a7d66d768dcc17c9be56c62/pyqt_openai/icon.ico -------------------------------------------------------------------------------- /pyqt_openai/img/import_prompt_with_csv_right_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjg30737/pyqt-openai/c4565d3beddb95a39a7d66d768dcc17c9be56c62/pyqt_openai/img/import_prompt_with_csv_right_form.png -------------------------------------------------------------------------------- /pyqt_openai/lang/translations.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | 5 | from qtpy.QtCore import QLocale 6 | 7 | from pyqt_openai import DEFAULT_LANGUAGE, LANGUAGE_DICT, LANGUAGE_FILE 8 | 9 | 10 | class WordsDict(dict): 11 | """Only used for release version 12 | to prevent KeyError. 13 | """ 14 | 15 | def __missing__(self, key): 16 | return key 17 | 18 | 19 | class LangClass: 20 | """LangClass is the class that manages the language of the application. 21 | It reads the language file and sets the language. 22 | """ 23 | 24 | TRANSLATIONS = WordsDict() 25 | 26 | @classmethod 27 | def lang_changed(cls, lang=None): 28 | with open(LANGUAGE_FILE, encoding="utf-8") as file: 29 | translations_data = json.load(file) 30 | 31 | if not lang: 32 | language = QLocale.system().name() 33 | if language not in translations_data: 34 | language = DEFAULT_LANGUAGE # Default language 35 | else: 36 | language = LANGUAGE_DICT[lang] 37 | 38 | cls.TRANSLATIONS = WordsDict(translations_data[language]) 39 | 40 | for k, v in LANGUAGE_DICT.items(): 41 | if v == language: 42 | return k 43 | -------------------------------------------------------------------------------- /pyqt_openai/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | 6 | # Get the absolute path of the current script file 7 | 8 | if __name__ == "__main__": 9 | script_path: str = os.path.abspath(__file__) 10 | 11 | # Get the root directory by going up one level from the script directory 12 | project_root: str = os.path.dirname(os.path.dirname(script_path)) 13 | 14 | sys.path.insert(0, project_root) 15 | sys.path.insert(0, os.getcwd()) # Add the current directory as well 16 | 17 | # for testing pyside6 18 | # os.environ['QT_API'] = 'pyside6' 19 | 20 | # for testing pyqt6 21 | # os.environ['QT_API'] = 'pyqt6' 22 | 23 | from qtpy.QtGui import QFont, QIcon, QPixmap 24 | from qtpy.QtSql import QSqlDatabase 25 | from qtpy.QtWidgets import QApplication, QSplashScreen 26 | 27 | from pyqt_openai import DEFAULT_APP_ICON 28 | from pyqt_openai.config_loader import CONFIG_MANAGER 29 | from pyqt_openai.mainWindow import MainWindow 30 | from pyqt_openai.sqlite import get_db_filename 31 | from pyqt_openai.updateSoftwareDialog import update_software 32 | from pyqt_openai.util.common import handle_exception 33 | 34 | 35 | # Application 36 | class App(QApplication): 37 | def __init__(self, *args): 38 | super().__init__(*args) 39 | self.setQuitOnLastWindowClosed(False) 40 | self.setWindowIcon(QIcon(DEFAULT_APP_ICON)) 41 | self.splash: QSplashScreen = QSplashScreen(QPixmap(DEFAULT_APP_ICON)) 42 | self.splash.show() 43 | 44 | self.__initQSqlDb() 45 | self.__initFont() 46 | 47 | self.__showMainWindow() 48 | self.splash.finish(self.main_window) 49 | 50 | update_software() 51 | 52 | def __initQSqlDb(self): 53 | # Set up the database and table model (you'll need to configure this part based on your database) 54 | self.__db: QSqlDatabase = QSqlDatabase.addDatabase("QSQLITE") 55 | self.__db.setDatabaseName(get_db_filename()) 56 | self.__db.open() 57 | 58 | def __initFont(self): 59 | font_family: str = CONFIG_MANAGER.get_general_property("font_family") or "Arial" 60 | font_size: int = int(CONFIG_MANAGER.get_general_property("font_size") or 12) 61 | QApplication.setFont(QFont(font_family, font_size)) 62 | 63 | def __showMainWindow(self): 64 | self.main_window: MainWindow = MainWindow() 65 | self.main_window.show() 66 | 67 | 68 | # Set the global exception handler 69 | sys.excepthook = handle_exception 70 | 71 | 72 | def main(): 73 | app: App = App(sys.argv) 74 | sys.exit(app.exec()) 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /pyqt_openai/prompt_res/alex_brogan.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "alex_brogan", 4 | "data": [ 5 | { 6 | "act": "sample_1", 7 | "prompt": "Identify the 20% of [topic or skill] that will yield 80% of the desired results and provide a focused learning plan to master it." 8 | }, 9 | { 10 | "act": "sample_2", 11 | "prompt": "Explain [topic or skill] in the simplest terms possible as if teaching it to a complete beginner. Identify gaps in my understanding and suggest resources to fill them." 12 | }, 13 | { 14 | "act": "sample_3", 15 | "prompt": "Create a study plan that mixes different topics or skills within [subject area] to help me develop a more robust understanding and facilitate connections between them." 16 | }, 17 | { 18 | "act": "sample_4", 19 | "prompt": "Design a spaced repetition schedule for me to effectively review [topic or skill] over time, ensuring better retention and recall." 20 | }, 21 | { 22 | "act": "sample_5", 23 | "prompt": "Help me create mental models or analogies to better understand and remember key concepts in [topic or skill]." 24 | }, 25 | { 26 | "act": "sample_6", 27 | "prompt": "Suggest various learning resources (e.g., videos, books, podcasts, interactive exercises) for [topic or skill] that cater to different learning styles." 28 | }, 29 | { 30 | "act": "sample_7", 31 | "prompt": "Provide me with a series of challenging questions or problems related to [topic or skill] to test my understanding and improve long-term retention." 32 | }, 33 | { 34 | "act": "sample_8", 35 | "prompt": "Transform key concepts or lessons from [topic or skill] into engaging stories or narratives to help me better remember and understand the material." 36 | }, 37 | { 38 | "act": "sample_9", 39 | "prompt": "Design a deliberate practice routine for [topic or skill], focusing on my weaknesses and providing regular feedback for improvement." 40 | }, 41 | { 42 | "act": "sample_10", 43 | "prompt": "Guide me through a visualization exercise to help me internalize [topic or skill] and imagine myself succesfully applying it in real-life situations." 44 | } 45 | ] 46 | } 47 | ] -------------------------------------------------------------------------------- /pyqt_openai/replicate_widget/replicateHome.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtGui import QFont 5 | from qtpy.QtWidgets import QLabel, QScrollArea, QVBoxLayout, QWidget 6 | 7 | from pyqt_openai import ( 8 | CONTEXT_DELIMITER, 9 | HOW_TO_REPLICATE, 10 | LARGE_LABEL_PARAM, 11 | MEDIUM_LABEL_PARAM, 12 | ) 13 | from pyqt_openai.lang.translations import LangClass 14 | from pyqt_openai.widgets.linkLabel import LinkLabel 15 | 16 | 17 | class ReplicateHome(QScrollArea): 18 | def __init__(self, parent=None): 19 | super().__init__(parent) 20 | self.__initUi() 21 | 22 | def __initUi(self): 23 | title = QLabel("Welcome to Replicate Page !", self) 24 | title.setFont(QFont(*LARGE_LABEL_PARAM)) 25 | title.setAlignment(Qt.AlignmentFlag.AlignCenter) 26 | 27 | description = QLabel( 28 | LangClass.TRANSLATIONS["Generate images with Replicate API."] 29 | + "\n" 30 | + LangClass.TRANSLATIONS[ 31 | "You can use a lot of models to generate images, only you need to have an API key." 32 | ] 33 | + CONTEXT_DELIMITER, 34 | ) 35 | 36 | description.setFont(QFont(*MEDIUM_LABEL_PARAM)) 37 | description.setAlignment(Qt.AlignmentFlag.AlignCenter) 38 | 39 | self.__manualLabel = LinkLabel() 40 | self.__manualLabel.setText("What is the Replicate & How to use it?") 41 | self.__manualLabel.setUrl(HOW_TO_REPLICATE) 42 | self.__manualLabel.setFont(QFont(*MEDIUM_LABEL_PARAM)) 43 | self.__manualLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) 44 | 45 | lay = QVBoxLayout() 46 | lay.addWidget(title) 47 | lay.addWidget(description) 48 | lay.addWidget(self.__manualLabel) 49 | lay.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter) 50 | self.setLayout(lay) 51 | 52 | mainWidget = QWidget() 53 | mainWidget.setLayout(lay) 54 | self.setWidget(mainWidget) 55 | self.setWidgetResizable(True) 56 | -------------------------------------------------------------------------------- /pyqt_openai/replicate_widget/replicateMainWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pyqt_openai.config_loader import CONFIG_MANAGER 4 | from pyqt_openai.replicate_widget.replicateHome import ReplicateHome 5 | from pyqt_openai.replicate_widget.replicateRightSideBar import ( 6 | ReplicateRightSideBarWidget, 7 | ) 8 | from pyqt_openai.widgets.imageMainWidget import ImageMainWidget 9 | 10 | 11 | class ReplicateMainWidget(ImageMainWidget): 12 | def __init__(self, parent=None): 13 | super().__init__(parent) 14 | self.__initUi() 15 | 16 | def __initUi(self): 17 | self._homePage = ReplicateHome() 18 | self._rightSideBarWidget = ReplicateRightSideBarWidget() 19 | 20 | self._setHomeWidget(self._homePage) 21 | self._setRightSideBarWidget(self._rightSideBarWidget) 22 | self._completeUi() 23 | 24 | def toggleHistory(self, f): 25 | super().toggleHistory(f) 26 | CONFIG_MANAGER.set_replicate_property("show_history", f) 27 | 28 | def toggleSetting(self, f): 29 | super().toggleSetting(f) 30 | CONFIG_MANAGER.set_replicate_property("show_setting", f) 31 | -------------------------------------------------------------------------------- /pyqt_openai/replicate_widget/replicateThread.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import QThread, Signal 4 | 5 | from pyqt_openai.globals import REPLICATE_CLIENT 6 | from pyqt_openai.models import ImagePromptContainer 7 | from pyqt_openai.util.common import generate_random_prompt 8 | 9 | 10 | class ReplicateThread(QThread): 11 | replyGenerated = Signal(ImagePromptContainer) 12 | errorGenerated = Signal(str) 13 | allReplyGenerated = Signal() 14 | 15 | def __init__( 16 | self, input_args, number_of_images, randomizing_prompt_source_arr=None, 17 | ): 18 | super().__init__() 19 | self.__input_args = input_args 20 | self.__stop = False 21 | 22 | self.__randomizing_prompt_source_arr = randomizing_prompt_source_arr 23 | 24 | self.__number_of_images = number_of_images 25 | 26 | def stop(self): 27 | self.__stop = True 28 | 29 | def run(self): 30 | try: 31 | for _ in range(self.__number_of_images): 32 | if self.__stop: 33 | break 34 | if self.__randomizing_prompt_source_arr is not None: 35 | self.__input_args["prompt"] = generate_random_prompt( 36 | self.__randomizing_prompt_source_arr, 37 | ) 38 | result = REPLICATE_CLIENT.get_image_response( 39 | model=self.__input_args["model"], input_args=self.__input_args, 40 | ) 41 | self.replyGenerated.emit(result) 42 | self.allReplyGenerated.emit() 43 | except Exception as e: 44 | self.errorGenerated.emit(str(e)) 45 | -------------------------------------------------------------------------------- /pyqt_openai/settings_dialog/settingsDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import ( 5 | QDialog, 6 | QDialogButtonBox, 7 | QHBoxLayout, 8 | QMessageBox, 9 | QStackedWidget, 10 | QVBoxLayout, 11 | QWidget, 12 | ) 13 | 14 | from pyqt_openai.lang.translations import LangClass 15 | from pyqt_openai.models import SettingsParamsContainer 16 | from pyqt_openai.settings_dialog.apiWidget import ApiWidget 17 | from pyqt_openai.settings_dialog.generalSettingsWidget import GeneralSettingsWidget 18 | from pyqt_openai.settings_dialog.voiceSettingsWidget import VoiceSettingsWidget 19 | from pyqt_openai.widgets.navWidget import NavBar 20 | 21 | 22 | class SettingsDialog(QDialog): 23 | def __init__(self, default_index=0, parent=None): 24 | super().__init__(parent) 25 | self.__initVal(default_index) 26 | self.__initUi() 27 | 28 | def __initVal(self, default_index): 29 | self.__default_index = default_index 30 | 31 | def __initUi(self): 32 | self.setWindowTitle(LangClass.TRANSLATIONS["Settings"]) 33 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 34 | 35 | self.__generalSettingsWidget = GeneralSettingsWidget() 36 | self.__apiWidget = ApiWidget() 37 | self.__voiceSettingsWidget = VoiceSettingsWidget() 38 | 39 | # Dialog buttons 40 | buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) 41 | buttonBox.accepted.connect(self.__accept) 42 | buttonBox.rejected.connect(self.reject) 43 | 44 | self.__stackedWidget = QStackedWidget() 45 | 46 | self.__navBar = NavBar(orientation=Qt.Orientation.Vertical) 47 | self.__navBar.add(LangClass.TRANSLATIONS["General"]) 48 | self.__navBar.add(LangClass.TRANSLATIONS["API Key"]) 49 | self.__navBar.add(LangClass.TRANSLATIONS["TTS-STT Settings"]) 50 | self.__navBar.itemClicked.connect(self.__currentWidgetChanged) 51 | 52 | self.__stackedWidget.addWidget(self.__generalSettingsWidget) 53 | self.__stackedWidget.addWidget(self.__apiWidget) 54 | self.__stackedWidget.addWidget(self.__voiceSettingsWidget) 55 | 56 | self.__stackedWidget.setCurrentIndex(self.__default_index) 57 | self.__navBar.setActiveButton(self.__default_index) 58 | 59 | lay = QHBoxLayout() 60 | lay.addWidget(self.__navBar) 61 | lay.addWidget(self.__stackedWidget) 62 | lay.setContentsMargins(0, 0, 0, 0) 63 | 64 | self.__mainWidget = QWidget() 65 | self.__mainWidget.setLayout(lay) 66 | 67 | lay = QVBoxLayout() 68 | lay.addWidget(self.__mainWidget) 69 | lay.addWidget(buttonBox) 70 | 71 | self.setLayout(lay) 72 | 73 | def __accept(self): 74 | # If DB file name is empty 75 | if self.__generalSettingsWidget.db.strip() == "": 76 | QMessageBox.critical( 77 | self, 78 | LangClass.TRANSLATIONS["Error"], 79 | LangClass.TRANSLATIONS["Database name cannot be empty."], 80 | ) 81 | else: 82 | self.accept() 83 | 84 | def getParam(self): 85 | return SettingsParamsContainer( 86 | **self.__generalSettingsWidget.getParam(), 87 | **self.__voiceSettingsWidget.getParam(), 88 | ) 89 | 90 | def __currentWidgetChanged(self, i): 91 | self.__stackedWidget.setCurrentIndex(i) 92 | self.__navBar.setActiveButton(i) 93 | -------------------------------------------------------------------------------- /pyqt_openai/settings_dialog/voiceSettingsWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtWidgets import ( 4 | QCheckBox, 5 | QComboBox, 6 | QDoubleSpinBox, 7 | QFormLayout, 8 | QGroupBox, 9 | QLabel, 10 | QSpinBox, 11 | QVBoxLayout, 12 | QWidget, 13 | ) 14 | 15 | from pyqt_openai import ( 16 | DEFAULT_HIGHLIGHT_TEXT_COLOR, 17 | EDGE_TTS_VOICE_TYPE, 18 | WHISPER_TTS_VOICE_SPEED_RANGE, 19 | WHISPER_TTS_VOICE_TYPE, 20 | ) 21 | from pyqt_openai.config_loader import CONFIG_MANAGER 22 | from pyqt_openai.lang.translations import LangClass 23 | 24 | 25 | class VoiceSettingsWidget(QWidget): 26 | def __init__(self, parent=None): 27 | super().__init__(parent) 28 | self.__initVal() 29 | self.__initUi() 30 | 31 | def __initVal(self): 32 | self.voice_provider = CONFIG_MANAGER.get_general_property("voice_provider") 33 | self.voice = CONFIG_MANAGER.get_general_property("voice") 34 | self.speed = CONFIG_MANAGER.get_general_property("voice_speed") 35 | self.auto_play = CONFIG_MANAGER.get_general_property("auto_play_voice") 36 | self.auto_stop_silence_duration = CONFIG_MANAGER.get_general_property( 37 | "auto_stop_silence_duration", 38 | ) 39 | 40 | def __initUi(self): 41 | ttsGrpBox = QGroupBox("Text to Speech") 42 | 43 | self.__voiceProviderCmbBox = QComboBox() 44 | self.__voiceProviderCmbBox.addItems(["OpenAI", "edge-tts"]) 45 | self.__voiceProviderCmbBox.setCurrentText(self.voice_provider) 46 | self.__voiceProviderCmbBox.currentTextChanged.connect( 47 | self.__voiceProviderChanged, 48 | ) 49 | 50 | # TODO LANGUAGE 51 | self.__warningLbl = QLabel( 52 | "You need to install mpv to use edge-tts. " 53 | "Link" 54 | "
Also edge-tts can only be used when run with python.", 55 | ) 56 | self.__warningLbl.setOpenExternalLinks(True) 57 | self.__warningLbl.setStyleSheet(f"color: {DEFAULT_HIGHLIGHT_TEXT_COLOR};") 58 | self.__warningLbl.setVisible(self.voice_provider == "edge-tts") 59 | 60 | detailsGroupBox = QGroupBox("Details") 61 | 62 | self.__voiceCmbBox = QComboBox() 63 | self.__voiceProviderChanged(self.voice_provider) 64 | self.__voiceCmbBox.setCurrentText(self.voice) 65 | 66 | self.__speedSpinBox = QDoubleSpinBox() 67 | self.__speedSpinBox.setRange(*WHISPER_TTS_VOICE_SPEED_RANGE) 68 | self.__speedSpinBox.setSingleStep(0.1) 69 | self.__speedSpinBox.setValue(float(self.speed)) 70 | 71 | # Auto-Play voice when response is received 72 | self.__autoPlayChkBox = QCheckBox( 73 | "Auto-Play Voice when Response is Received (Work in Progress)", 74 | ) 75 | self.__autoPlayChkBox.setChecked(self.auto_play) 76 | # TODO implement auto-play voice in v1.8.0 77 | self.__autoPlayChkBox.setEnabled(False) 78 | 79 | lay = QFormLayout() 80 | lay.addRow(LangClass.TRANSLATIONS["Voice"], self.__voiceCmbBox) 81 | lay.addRow(LangClass.TRANSLATIONS["Voice Speed"], self.__speedSpinBox) 82 | lay.addRow(self.__autoPlayChkBox) 83 | detailsGroupBox.setLayout(lay) 84 | 85 | lay = QFormLayout() 86 | lay.addRow(LangClass.TRANSLATIONS["Voice Provider"], self.__voiceProviderCmbBox) 87 | lay.addRow(self.__warningLbl) 88 | lay.addRow(detailsGroupBox) 89 | 90 | ttsGrpBox.setLayout(lay) 91 | 92 | sttGrpBox = QGroupBox("Speech to Text") 93 | 94 | # Allow user to determine Auto-Stop Silence Duration 95 | self.__autoStopSilenceDurationSpinBox = QSpinBox() 96 | self.__autoStopSilenceDurationSpinBox.setRange(3, 10) 97 | self.__autoStopSilenceDurationSpinBox.setValue(self.auto_stop_silence_duration) 98 | # TODO implement auto-play voice in v1.8.0 99 | self.__autoStopSilenceDurationSpinBox.setEnabled(False) 100 | 101 | lay = QFormLayout() 102 | lay.addRow( 103 | "Auto-Stop Silence Duration (Work in Progress)", 104 | self.__autoStopSilenceDurationSpinBox, 105 | ) 106 | 107 | sttGrpBox.setLayout(lay) 108 | 109 | lay = QVBoxLayout() 110 | lay.addWidget(ttsGrpBox) 111 | lay.addWidget(sttGrpBox) 112 | 113 | self.setLayout(lay) 114 | 115 | def getParam(self): 116 | return { 117 | "voice_provider": self.__voiceProviderCmbBox.currentText(), 118 | "voice": self.__voiceCmbBox.currentText(), 119 | "voice_speed": self.__speedSpinBox.value(), 120 | "auto_play_voice": self.__autoPlayChkBox.isChecked(), 121 | "auto_stop_silence_duration": self.__autoStopSilenceDurationSpinBox.value(), 122 | } 123 | 124 | def __voiceProviderChanged(self, text): 125 | f = text == "OpenAI" 126 | if f: 127 | self.__voiceCmbBox.clear() 128 | self.__voiceCmbBox.addItems(WHISPER_TTS_VOICE_TYPE) 129 | else: 130 | self.__voiceCmbBox.clear() 131 | self.__voiceCmbBox.addItems(EDGE_TTS_VOICE_TYPE) 132 | self.__warningLbl.setVisible(not f) 133 | -------------------------------------------------------------------------------- /pyqt_openai/util/button_style_helper.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtGui import QColor, QPalette, qGray 6 | from qtpy.QtWidgets import ( 7 | QApplication, 8 | ) 9 | 10 | from pyqt_openai import ( 11 | DEFAULT_BUTTON_CHECKED_COLOR, 12 | DEFAULT_BUTTON_HOVER_COLOR, 13 | DEFAULT_BUTTON_PRESSED_COLOR, 14 | ) 15 | 16 | if TYPE_CHECKING: 17 | from qtpy.QtWidgets import ( 18 | QWidget, 19 | ) 20 | 21 | 22 | class ButtonStyleHelper: 23 | def __init__(self, base_widget: QWidget = None): 24 | self.__baseWidget = base_widget 25 | self.__initVal() 26 | 27 | def __initVal(self): 28 | # to set size accordance with scale 29 | sc = QApplication.screens()[0] 30 | sc.logicalDotsPerInchChanged.connect(self.__scaleChanged) 31 | self.__size = sc.logicalDotsPerInch() // 4 32 | self.__padding = self.__border_radius = self.__size // 10 33 | self.__background_color = "transparent" 34 | self.__icon = "" 35 | self.__animation = "" 36 | if self.__baseWidget: 37 | self.__initColorByBaseWidget() 38 | else: 39 | self.__hover_color = DEFAULT_BUTTON_HOVER_COLOR 40 | self.__pressed_color = DEFAULT_BUTTON_PRESSED_COLOR 41 | self.__checked_color = DEFAULT_BUTTON_CHECKED_COLOR 42 | 43 | def __initColorByBaseWidget(self): 44 | self.__base_color = self.__baseWidget.palette().color(QPalette.ColorRole.Base) 45 | self.__hover_color = self.__getHoverColor(self.__base_color) 46 | self.__pressed_color = self.__getPressedColor(self.__base_color) 47 | self.__checked_color = self.__getPressedColor(self.__base_color) 48 | 49 | def __getColorByFactor(self, base_color, factor): 50 | r, g, b = base_color.red(), base_color.green(), base_color.blue() 51 | gray = qGray(r, g, b) 52 | if gray > 255 // 2: 53 | color = base_color.darker(factor) 54 | else: 55 | color = base_color.lighter(factor) 56 | return color 57 | 58 | def __getHoverColor(self, base_color): 59 | hover_factor = 120 60 | hover_color = self.__getColorByFactor(base_color, hover_factor) 61 | return hover_color.name() 62 | 63 | def __getPressedColor(self, base_color): 64 | pressed_factor = 130 65 | pressed_color = self.__getColorByFactor(base_color, pressed_factor) 66 | return pressed_color.name() 67 | 68 | def __getCheckedColor(self, base_color): 69 | return self.__getPressedColor(self.__base_color) 70 | 71 | def __getButtonTextColor(self, base_color): 72 | r, g, b = ( 73 | base_color.red() ^ 255, 74 | base_color.green() ^ 255, 75 | base_color.blue() ^ 255, 76 | ) 77 | if r == g == b: 78 | text_color = QColor(r, g, b) 79 | elif qGray(r, g, b) > 255 // 2: 80 | text_color = QColor(255, 255, 255) 81 | else: 82 | text_color = QColor(0, 0, 0) 83 | return text_color.name() 84 | 85 | def styleInit(self): 86 | self.__btn_style = f""" 87 | QAbstractButton 88 | {{ 89 | border: 0; 90 | width: {self.__size}px; 91 | height: {self.__size}px; 92 | background-color: {self.__background_color}; 93 | border-radius: {self.__border_radius}px; 94 | padding: {self.__padding}px; 95 | }} 96 | QAbstractButton:hover 97 | {{ 98 | background-color: {self.__hover_color}; 99 | }} 100 | QAbstractButton:pressed 101 | {{ 102 | background-color: {self.__pressed_color}; 103 | }} 104 | QAbstractButton:checked 105 | {{ 106 | background-color: {self.__checked_color}; 107 | border: none; 108 | }} 109 | """ 110 | return self.__btn_style 111 | 112 | def setPadding(self, padding: int): 113 | self.__padding = padding 114 | 115 | def setBorderRadius(self, border_radius: int): 116 | self.__border_radius = border_radius 117 | 118 | def setBackground(self, background=None): 119 | if background: 120 | self.__background_color = background 121 | else: 122 | self.__background_color = self.__base_color.name() 123 | 124 | def setAsCircle(self, height): 125 | self.__border_radius = height // 2 126 | 127 | def __scaleChanged(self, dpi): 128 | self.__size = dpi // 4 129 | -------------------------------------------------------------------------------- /pyqt_openai/util/llamaindex.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os.path 4 | from typing import TYPE_CHECKING 5 | 6 | from llama_index.core import SimpleDirectoryReader, VectorStoreIndex 7 | from pyqt_openai.config_loader import CONFIG_MANAGER 8 | 9 | if TYPE_CHECKING: 10 | from llama_index.core.base.base_query_engine import BaseQueryEngine 11 | 12 | 13 | class LlamaIndexWrapper: 14 | def __init__(self, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | self._directory: str = "" 17 | self._query_engine: BaseQueryEngine | None = None 18 | self._index: VectorStoreIndex | None = None 19 | 20 | def set_directory( 21 | self, 22 | directory: str, 23 | ext: list[str] | None = None, 24 | ) -> None: 25 | if not ext: 26 | default_ext = CONFIG_MANAGER.get_general_property("llama_index_supported_formats") 27 | ext = default_ext if default_ext else [] 28 | assert ext, "llama_index_supported_formats is not set" 29 | self._directory = directory 30 | documents = SimpleDirectoryReader( 31 | input_dir=self._directory, 32 | required_exts=ext, 33 | ).load_data() 34 | self._index = VectorStoreIndex.from_documents(documents) 35 | 36 | def is_query_engine_set(self) -> bool: 37 | return self._query_engine is not None 38 | 39 | def set_query_engine( 40 | self, 41 | streaming: bool = False, 42 | similarity_top_k: int = 3, 43 | ): 44 | if self._index is None: 45 | raise Exception( 46 | "Index must be initialized first. Call set_directory or set_files first.", 47 | ) 48 | try: 49 | self._query_engine = self._index.as_query_engine( 50 | streaming=streaming, 51 | similarity_top_k=similarity_top_k, 52 | ) 53 | except Exception as e: 54 | raise Exception("Error in setting query engine") from e 55 | 56 | def get_directory(self) -> str: 57 | """This function returns the directory path. 58 | If directory does not exist, it will return the empty string. 59 | """ 60 | return ( 61 | self._directory 62 | if self._directory and os.path.exists(self._directory) 63 | else "" 64 | ) 65 | 66 | def get_response(self, text: str) -> str: 67 | try: 68 | if self._query_engine: 69 | resp = self._query_engine.query( 70 | text, 71 | ) 72 | return resp 73 | raise Exception( 74 | "Query engine not initialized. Maybe you need to set the directory first. Check the directory path.", 75 | ) 76 | except Exception as e: 77 | return str(e) 78 | -------------------------------------------------------------------------------- /pyqt_openai/util/replicate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import base64 4 | import os 5 | 6 | import replicate 7 | import requests 8 | 9 | from pyqt_openai.models import ImagePromptContainer 10 | 11 | 12 | def download_image_as_base64(url: str): 13 | response = requests.get(url) 14 | response.raise_for_status() # Check if the URL is correct and raise an exception if there is a problem 15 | image_data = response.content 16 | base64_encoded = base64.b64decode(base64.b64encode(image_data).decode("utf-8")) 17 | return base64_encoded 18 | 19 | 20 | class ReplicateWrapper: 21 | def __init__(self, api_key): 22 | self.__api_key = api_key 23 | 24 | @property 25 | def api_key(self): 26 | return self.__api_key 27 | 28 | @api_key.setter 29 | def api_key(self, value): 30 | self.__api_key = value 31 | os.environ["REPLICATE_API_KEY"] = self.__api_key 32 | 33 | def is_available(self): 34 | return True if self.__api_key is not None else False 35 | 36 | def get_image_response(self, model, input_args): 37 | try: 38 | model = ( 39 | "lucataco/playground-v2.5-1024px-aesthetic:419269784d9e00c56e5b09747cfc059a421e0c044d5472109e129b746508c365" 40 | if model is None 41 | else model 42 | ) 43 | 44 | input_args = ( 45 | { 46 | "width": 768, 47 | "height": 768, 48 | "prompt": "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", 49 | "negative_prompt": "ugly, deformed, noisy, blurry, distorted", 50 | } 51 | if input_args is None 52 | else input_args 53 | ) 54 | 55 | input_args["num_outputs"] = ( 56 | 1 if "num_outputs" not in input_args else input_args["num_outputs"] 57 | ) 58 | input_args["guidance_scale"] = ( 59 | 3 60 | if "guidance_scale" not in input_args 61 | else input_args["guidance_scale"] 62 | ) 63 | input_args["apply_watermark"] = ( 64 | True 65 | if "apply_watermark" not in input_args 66 | else input_args["apply_watermark"] 67 | ) 68 | input_args["prompt_strength"] = ( 69 | 0.8 70 | if "prompt_strength" not in input_args 71 | else input_args["prompt_strength"] 72 | ) 73 | input_args["num_inference_steps"] = ( 74 | 50 75 | if "num_inference_steps" not in input_args 76 | else input_args["num_inference_steps"] 77 | ) 78 | input_args["refine"] = ( 79 | "expert_ensemble_refiner" 80 | if "refine" not in input_args 81 | else input_args["refine"] 82 | ) 83 | 84 | output = replicate.run(model, input=input_args) 85 | 86 | if output is not None and len(output) > 0: 87 | arg = { 88 | "model": model, 89 | "prompt": input_args["prompt"], 90 | "size": f"{input_args['width']}x{input_args['height']}", 91 | "quality": "high", 92 | "data": download_image_as_base64(output[0]), 93 | "style": "", 94 | "revised_prompt": "", 95 | "width": input_args["width"], 96 | "height": input_args["height"], 97 | "negative_prompt": input_args["negative_prompt"], 98 | } 99 | arg = ImagePromptContainer(**arg) 100 | return arg 101 | raise Exception("No output") 102 | except Exception as e: 103 | raise e 104 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/APIInputButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import colorsys 4 | 5 | from qtpy.QtWidgets import QPushButton 6 | 7 | from pyqt_openai.settings_dialog.settingsDialog import SettingsDialog 8 | 9 | 10 | class APIInputButton(QPushButton): 11 | """Stylish button for opening the API settings dialog.""" 12 | def __init__( 13 | self, 14 | base_color: str = "#007BFF", 15 | ): 16 | super().__init__() 17 | self.setObjectName("modernButton") 18 | self.base_color: str = base_color # Default base color 19 | self.__initUi() 20 | 21 | def __initUi(self): 22 | self.clicked.connect( 23 | lambda _: SettingsDialog(default_index=1, parent=self).exec(), 24 | ) 25 | self.updateStylesheet(self.base_color) 26 | 27 | def updateStylesheet( 28 | self, 29 | base_color: str, 30 | ): 31 | """Generate dynamic styles based on the base color.""" 32 | hover_color: str = self.adjust_brightness(base_color, 0.8) # Brighten 33 | pressed_color: str = self.adjust_brightness(base_color, 0.6) # Darken 34 | border_color: str = pressed_color # Use the same color for border 35 | 36 | # Dynamically generated stylesheet 37 | self.setStyleSheet( 38 | f""" 39 | QPushButton#modernButton {{ 40 | background-color: {base_color}; 41 | color: white; 42 | border-radius: 8px; 43 | padding: 10px 20px; 44 | font-size: 16px; 45 | font-family: "Arial"; 46 | font-weight: bold; 47 | border: 2px solid {base_color}; 48 | }} 49 | QPushButton#modernButton:hover {{ 50 | background-color: {hover_color}; 51 | border-color: {hover_color}; 52 | }} 53 | QPushButton#modernButton:pressed {{ 54 | background-color: {pressed_color}; 55 | border-color: {border_color}; 56 | }} 57 | """, 58 | ) 59 | 60 | def adjust_brightness( 61 | self, 62 | hex_color: str, 63 | factor: float, 64 | ) -> str: 65 | """Adjust the brightness of a given hex color.""" 66 | hex_color = hex_color.lstrip("#") 67 | r, g, b = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) 68 | 69 | # Convert RGB to HLS 70 | h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0) 71 | 72 | # Adjust lightness 73 | l = max(0, min(1, l * factor)) # Ensure lightness stays within 0-1 74 | r, g, b = colorsys.hls_to_rgb(h, l, s) 75 | 76 | # Convert RGB back to hex 77 | return f"#{int(r * 255):02X}{int(g * 255):02X}{int(b * 255):02X}" 78 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/animationButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import QEasingCurve, QPropertyAnimation 6 | from qtpy.QtWidgets import QGraphicsOpacityEffect, QPushButton 7 | 8 | if TYPE_CHECKING: 9 | from qtpy.QtWidgets import QWidget 10 | 11 | 12 | class AnimationButton(QPushButton): 13 | def __init__( 14 | self, 15 | text: str = "Other API", 16 | duration: int = 1000, 17 | start_value: float = 1, 18 | end_value: float = 0.5, 19 | parent: QWidget | None = None, 20 | ): 21 | super().__init__(text, parent) 22 | 23 | # Apply an opacity effect to the button 24 | self.opacity_effect: QGraphicsOpacityEffect = QGraphicsOpacityEffect() 25 | self.setGraphicsEffect(self.opacity_effect) 26 | 27 | # Create the animation for the opacity effect 28 | self.opacity_animation: QPropertyAnimation = QPropertyAnimation(self.opacity_effect, b"opacity") 29 | self.opacity_animation.setDuration( 30 | duration, 31 | ) # Duration of one animation cycle (in milliseconds) 32 | self.opacity_animation.setStartValue(start_value) # Start with full opacity 33 | self.opacity_animation.setEndValue(end_value) # End with lower opacity 34 | self.opacity_animation.setEasingCurve( 35 | QEasingCurve.Type.InOutQuad, 36 | ) # Smooth transition 37 | 38 | # Set the animation to alternate between fading in and out 39 | self.opacity_animation.setDirection( 40 | QPropertyAnimation.Direction.Forward, 41 | ) # Start direction 42 | 43 | # Connect the animation's finished signal to reverse direction 44 | self.opacity_animation.finished.connect(self.reverse_animation_direction) 45 | 46 | # Start the animation 47 | self.opacity_animation.start() 48 | 49 | def reverse_animation_direction(self): 50 | # Reverse the direction of the animation each time it finishes 51 | if self.opacity_animation.direction() == QPropertyAnimation.Direction.Forward: 52 | self.opacity_animation.setDirection(QPropertyAnimation.Direction.Backward) 53 | else: 54 | self.opacity_animation.setDirection(QPropertyAnimation.Direction.Forward) 55 | self.opacity_animation.start() 56 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/button.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtGui import QColor, QIcon 6 | from qtpy.QtWidgets import QGraphicsColorizeEffect, QPushButton 7 | 8 | from pyqt_openai.util.button_style_helper import ButtonStyleHelper 9 | 10 | if TYPE_CHECKING: 11 | from qtpy.QtCore import QEvent, QObject 12 | from qtpy.QtWidgets import QWidget 13 | 14 | 15 | class Button(QPushButton): 16 | def __init__( 17 | self, 18 | base_widget: QWidget | None = None, 19 | parent: QWidget | None = None, 20 | ): 21 | super().__init__(parent) 22 | self.style_helper: ButtonStyleHelper = ButtonStyleHelper(base_widget) 23 | self.setStyleSheet(self.style_helper.styleInit()) 24 | self.installEventFilter(self) 25 | 26 | def setStyleAndIcon( 27 | self, 28 | icon: str, 29 | ): 30 | self.style_helper.__icon = icon 31 | self.setStyleSheet(self.style_helper.styleInit()) 32 | self.setIcon(QIcon(icon)) 33 | 34 | def eventFilter( 35 | self, 36 | obj: QObject, 37 | event: QEvent, 38 | ): 39 | if obj == self: 40 | if event.type() == 98: # Event type for EnableChange 41 | effect: QGraphicsColorizeEffect = QGraphicsColorizeEffect() 42 | effect.setColor(QColor(255, 255, 255)) 43 | if self.isEnabled(): 44 | effect.setStrength(0) 45 | else: 46 | effect.setStrength(1) 47 | effect.setColor(QColor(150, 150, 150)) 48 | self.setGraphicsEffect(effect) 49 | return super().eventFilter(obj, event) 50 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/checkBoxListWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Qt, Signal 6 | from qtpy.QtWidgets import QListWidget, QListWidgetItem 7 | 8 | if TYPE_CHECKING: 9 | from qtpy.QtWidgets import QWidget 10 | 11 | 12 | class CheckBoxListWidget(QListWidget): 13 | checkedSignal = Signal(int, Qt.CheckState) 14 | 15 | def __init__( 16 | self, 17 | parent: QWidget | None = None, 18 | ): 19 | super().__init__(parent) 20 | self.itemChanged.connect(self.__sendCheckedSignal) 21 | 22 | def __sendCheckedSignal( 23 | self, 24 | item: QListWidgetItem, 25 | ): 26 | r_idx: int = self.row(item) 27 | state: Qt.CheckState = item.checkState() 28 | self.checkedSignal.emit(r_idx, state) 29 | 30 | def addItems( 31 | self, 32 | items: list[str], 33 | checked: bool = False, 34 | ) -> None: 35 | """Add items to the list widget. 36 | If checked is True, the items will be checked. 37 | """ 38 | for item in items: 39 | self.addItem(item, checked=checked) 40 | 41 | def addItem( 42 | self, 43 | item: str | QListWidgetItem, 44 | checked: bool = False, 45 | ) -> None: 46 | if isinstance(item, str): 47 | item = QListWidgetItem(item) 48 | item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) 49 | if checked: 50 | item.setCheckState(Qt.CheckState.Checked) 51 | else: 52 | item.setCheckState(Qt.CheckState.Unchecked) 53 | super().addItem(item) 54 | 55 | def toggleState( 56 | self, 57 | state: Qt.CheckState, 58 | ): 59 | for i in range(self.count()): 60 | item: QListWidgetItem = self.item(i) 61 | state = Qt.CheckState(state) 62 | if item.checkState() != state: 63 | item.setCheckState(state) 64 | 65 | def getCheckedRows(self) -> list[int]: 66 | return self.__getFlagRows(Qt.CheckState.Checked) 67 | 68 | def getUncheckedRows(self) -> list[int]: 69 | return self.__getFlagRows(Qt.CheckState.Unchecked) 70 | 71 | def __getFlagRows( 72 | self, 73 | flag: Qt.CheckState, 74 | ) -> list[int]: 75 | flag_lst: list[int] = [] 76 | for i in range(self.count()): 77 | item: QListWidgetItem = self.item(i) 78 | if item.checkState() == flag: 79 | flag_lst.append(i) 80 | 81 | return flag_lst 82 | 83 | def removeCheckedRows(self): 84 | self.__removeFlagRows(Qt.CheckState.Checked) 85 | 86 | def removeUncheckedRows(self): 87 | self.__removeFlagRows(Qt.CheckState.Unchecked) 88 | 89 | def __removeFlagRows( 90 | self, 91 | flag: Qt.CheckState, 92 | ): 93 | flag_lst = self.__getFlagRows(flag) 94 | flag_lst = reversed(flag_lst) 95 | for i in flag_lst: 96 | self.takeItem(i) 97 | 98 | def getCheckedItems(self) -> list[QListWidgetItem]: 99 | return [self.item(i) for i in self.getCheckedRows()] 100 | 101 | def getCheckedItemsText( 102 | self, 103 | empty_str: str = "", 104 | include_empty: bool = True, 105 | ) -> list[str]: 106 | result: list[str] = [item.text() if item else empty_str for item in self.getCheckedItems()] 107 | if include_empty: 108 | return result 109 | return [text for text in result if text] 110 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/circleProfileImage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtGui import QBitmap, QPainter, QPixmap 7 | from qtpy.QtWidgets import QLabel 8 | 9 | if TYPE_CHECKING: 10 | from qtpy.QtWidgets import QWidget 11 | 12 | 13 | class RoundedImage(QLabel): 14 | def __init__( 15 | self, 16 | parent: QWidget | None = None, 17 | ): 18 | super().__init__(parent) 19 | self.__initVal() 20 | self.__initUi() 21 | 22 | def __initVal(self): 23 | self.__pixmap: str = "" 24 | self.__mask: str = "" 25 | 26 | def __initUi(self): 27 | pass 28 | 29 | def setImage( 30 | self, 31 | filename: str, 32 | ): 33 | # Load the image and set it as the pixmap for the label 34 | self.__pixmap: QPixmap = QPixmap(filename) 35 | self.__pixmap = self.__pixmap.scaled( 36 | self.__pixmap.width(), 37 | self.__pixmap.height(), 38 | Qt.AspectRatioMode.KeepAspectRatio, 39 | Qt.TransformationMode.SmoothTransformation, 40 | ) 41 | # Create a mask the same shape as the image 42 | self.__mask: QBitmap = QBitmap(self.__pixmap.size()) 43 | 44 | # Create a QPainter to draw the mask 45 | self.__painter: QPainter = QPainter(self.__mask) 46 | self.__painter.setRenderHint(QPainter.RenderHint.Antialiasing) 47 | self.__painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) 48 | self.__painter.fillRect(self.__mask.rect(), Qt.GlobalColor.white) 49 | 50 | # Draw a black, rounded rectangle on the mask 51 | self.__painter.setPen(Qt.GlobalColor.black) 52 | self.__painter.setBrush(Qt.GlobalColor.black) 53 | self.__painter.drawRoundedRect( 54 | self.__pixmap.rect(), 55 | self.__pixmap.size().width(), 56 | self.__pixmap.size().height(), 57 | ) 58 | self.__painter.end() 59 | 60 | # Apply the mask to the image 61 | self.__pixmap.setMask(self.__mask) 62 | self.setPixmap(self.__pixmap) 63 | self.setScaledContents(True) 64 | 65 | def getImage(self) -> QPixmap: 66 | return self.__pixmap 67 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/fileTableDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtWidgets import QDialog, QHeaderView, QTableWidget, QVBoxLayout 6 | 7 | if TYPE_CHECKING: 8 | from qtpy.QtWidgets import QWidget 9 | 10 | 11 | # TODO v1.8.0 12 | class FileTableDialog(QDialog): 13 | def __init__( 14 | self, 15 | parent: QWidget | None = None, 16 | ): 17 | super().__init__(parent) 18 | self.__initUi() 19 | 20 | def __initUi(self): 21 | # TODO LANGUAGE 22 | self.setWindowTitle("File List (Working)") 23 | # File tables 24 | self.__fileTable = QTableWidget() 25 | self.__fileTable.setColumnCount(2) 26 | self.__fileTable.setHorizontalHeaderLabels(["File Name", "Type"]) 27 | self.__fileTable.setColumnWidth(0, 300) 28 | self.__fileTable.setColumnWidth(1, 100) 29 | self.__fileTable.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) 30 | self.__fileTable.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) 31 | self.__fileTable.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) 32 | self.__fileTable.setSortingEnabled(True) 33 | self.__fileTable.setAlternatingRowColors(True) 34 | self.__fileTable.setShowGrid(False) 35 | self.__fileTable.verticalHeader().hide() 36 | self.__fileTable.horizontalHeader().setStretchLastSection(True) 37 | self.__fileTable.horizontalHeader().setHighlightSections(False) 38 | self.__fileTable.horizontalHeader().setSectionsClickable(True) 39 | self.__fileTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) 40 | self.__fileTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) 41 | lay = QVBoxLayout() 42 | lay.addWidget(self.__fileTable) 43 | self.setLayout(lay) 44 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/inputDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import ( 5 | QDialog, 6 | QHBoxLayout, 7 | QLineEdit, 8 | QPushButton, 9 | QVBoxLayout, 10 | QWidget, 11 | ) 12 | 13 | from pyqt_openai.lang.translations import LangClass 14 | from pyqt_openai.util.common import getSeparator 15 | 16 | 17 | class InputDialog(QDialog): 18 | def __init__( 19 | self, 20 | title: str, 21 | text: str, 22 | parent: QWidget | None = None, 23 | ): 24 | super().__init__(parent) 25 | self.__initUi(title, text) 26 | 27 | def __initUi( 28 | self, 29 | title: str, 30 | text: str, 31 | ): 32 | self.setWindowTitle(title) 33 | self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint) 34 | 35 | self.__newName: QLineEdit = QLineEdit(text) 36 | self.__newName.textChanged.connect(self.__setAccept) 37 | sep: QWidget = getSeparator("horizontal") 38 | 39 | self.__okBtn: QPushButton = QPushButton(LangClass.TRANSLATIONS["OK"]) 40 | self.__okBtn.clicked.connect(self.accept) 41 | 42 | cancelBtn: QPushButton = QPushButton(LangClass.TRANSLATIONS["Cancel"]) 43 | cancelBtn.clicked.connect(self.close) 44 | 45 | lay: QHBoxLayout = QHBoxLayout() 46 | lay.addWidget(self.__okBtn) 47 | lay.addWidget(cancelBtn) 48 | lay.setAlignment(Qt.AlignmentFlag.AlignRight) 49 | lay.setContentsMargins(0, 0, 0, 0) 50 | 51 | okCancelWidget: QWidget = QWidget() 52 | okCancelWidget.setLayout(lay) 53 | 54 | lay: QVBoxLayout = QVBoxLayout() 55 | lay.addWidget(self.__newName) 56 | lay.addWidget(sep) 57 | lay.addWidget(okCancelWidget) 58 | 59 | self.setLayout(lay) 60 | 61 | def getText(self): 62 | return self.__newName.text() 63 | 64 | def __setAccept( 65 | self, 66 | text: str, 67 | ): 68 | self.__okBtn.setEnabled(text.strip() != "") 69 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/linkLabel.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import QUrl, Qt 6 | from qtpy.QtGui import QDesktopServices 7 | 8 | from pyqt_openai import DEFAULT_LINK_COLOR, DEFAULT_LINK_HOVER_COLOR 9 | from pyqt_openai.widgets.svgLabel import SvgLabel 10 | 11 | if TYPE_CHECKING: 12 | from qtpy.QtGui import QMouseEvent 13 | from qtpy.QtWidgets import QWidget 14 | 15 | 16 | class LinkLabel(SvgLabel): 17 | def __init__( 18 | self, 19 | parent: QWidget | None = None, 20 | ): 21 | super().__init__(parent) 22 | self.__url: str = "127.0.0.1" 23 | self.setStyleSheet( 24 | f"QLabel {{ color: {DEFAULT_LINK_COLOR}; }} QLabel:hover {{ color: {DEFAULT_LINK_HOVER_COLOR}; }}", 25 | ) 26 | 27 | def setUrl( 28 | self, 29 | url: str, 30 | ): 31 | self.__url = url 32 | 33 | def mouseReleaseEvent( 34 | self, 35 | event: QMouseEvent, 36 | ): 37 | v: Qt.MouseButton = Qt.MouseButton.LeftButton 38 | if event.button() == v: 39 | QDesktopServices.openUrl(QUrl(self.__url)) 40 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/modelInputManualDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtGui import QFont 7 | from qtpy.QtWidgets import QDialog, QHBoxLayout, QLabel 8 | 9 | from pyqt_openai import DEFAULT_WARNING_COLOR, SMALL_LABEL_PARAM 10 | 11 | if TYPE_CHECKING: 12 | from qtpy.QtWidgets import QWidget 13 | 14 | 15 | class ModelInputManualDialog(QDialog): 16 | def __init__( 17 | self, 18 | parent: QWidget | None = None, 19 | ): 20 | super().__init__(parent) 21 | self.__initVal() 22 | self.__initUi() 23 | 24 | def __initVal(self): 25 | self.__warningMessage = ( 26 | "💡 Tip: For models other than OpenAI and Anthropic, enter the model name as " 27 | "[ProviderName]/[ModelName].

" 28 | "🔗 For details on ProviderName and ModelName, check out the " 29 | "LiteLLM documentation! 😊

" 30 | "⚠️ Note: Some models may not support JSON Mode or LlamaIndex features." 31 | ) 32 | 33 | def __initUi(self): 34 | self.setWindowTitle("Model Input Manual") 35 | self.__warningLbl: QLabel = QLabel() 36 | self.__warningLbl.setStyleSheet( 37 | f"color: {DEFAULT_WARNING_COLOR};", 38 | ) # Text color remains orange for visibility. 39 | self.__warningLbl.setWordWrap(True) 40 | self.__warningLbl.setFont(QFont(*SMALL_LABEL_PARAM)) 41 | self.__warningLbl.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) 42 | self.__warningLbl.setOpenExternalLinks(True) # Enable hyperlink functionality. 43 | self.__warningLbl.setText(self.__warningMessage) # Ensure HTML is passed as text. 44 | 45 | lay = QHBoxLayout() 46 | lay.addWidget(self.__warningLbl) 47 | self.setLayout(lay) 48 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/navWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Qt, Signal 6 | from qtpy.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget 7 | 8 | if TYPE_CHECKING: 9 | from qtpy.QtGui import QFont 10 | 11 | 12 | class NavBar(QWidget): 13 | itemClicked = Signal(int) # Signal to emit the index when an item is clicked 14 | 15 | def __init__( 16 | self, 17 | parent: QWidget | None = None, 18 | orientation: Qt.Orientation = Qt.Orientation.Horizontal, 19 | ): 20 | super().__init__(parent) 21 | self.__initVal() 22 | self.__initUi(orientation) 23 | 24 | def __initVal(self): 25 | self.__buttons: list[QPushButton] = [] # List to store button references 26 | 27 | def __initUi(self, orientation: Qt.Orientation): 28 | if orientation == Qt.Orientation.Horizontal: 29 | lay = QHBoxLayout() 30 | lay.setAlignment(Qt.AlignmentFlag.AlignLeft) 31 | else: 32 | lay = QVBoxLayout() 33 | lay.setAlignment(Qt.AlignmentFlag.AlignTop) 34 | lay.setContentsMargins(0, 0, 0, 0) 35 | self.setLayout(lay) 36 | 37 | def add( 38 | self, 39 | name: str, 40 | ): 41 | """Add a new navigation item.""" 42 | button: QPushButton = QPushButton(name) 43 | button_style = """ 44 | QPushButton { 45 | border: none; 46 | background-color: transparent; 47 | font-family: "Arial"; 48 | font-size: 16px; 49 | padding: 10px 15px; 50 | } 51 | QPushButton:hover { 52 | color: #007BFF; /* Highlight color */ 53 | } 54 | """ 55 | button.setStyleSheet(button_style) 56 | index = len(self.__buttons) 57 | button.clicked.connect(lambda: self.itemClicked.emit(index)) 58 | self.layout().addWidget(button) 59 | self.__buttons.append(button) 60 | 61 | def setActiveButton( 62 | self, 63 | active_index: int, 64 | ): 65 | """Set the active button as bold.""" 66 | for index, button in enumerate(self.__buttons): 67 | font: QFont = button.font() 68 | font.setBold(index == active_index) 69 | button.setFont(font) 70 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/normalImageView.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtCore import Qt 6 | from qtpy.QtGui import QPixmap 7 | from qtpy.QtWidgets import QGraphicsScene, QGraphicsView 8 | 9 | if TYPE_CHECKING: 10 | from qtpy.QtGui import QResizeEvent 11 | from qtpy.QtWidgets import QGraphicsPixmapItem 12 | 13 | 14 | class NormalImageView(QGraphicsView): 15 | def __init__(self): 16 | super().__init__() 17 | self.__aspectRatioMode: Qt.AspectRatioMode = Qt.AspectRatioMode.KeepAspectRatio 18 | self.__initVal() 19 | 20 | def __initVal(self): 21 | self._scene = QGraphicsScene() 22 | self._p = QPixmap() 23 | self._item: str = "" 24 | 25 | def setFilename( 26 | self, 27 | filename: str, 28 | ): 29 | if filename == "": 30 | pass 31 | else: 32 | self._p = QPixmap(filename) 33 | self._setPixmap(self._p) 34 | 35 | def setPixmap( 36 | self, 37 | p: QPixmap, 38 | ): 39 | self._setPixmap(p) 40 | 41 | def _setPixmap( 42 | self, 43 | p: QPixmap, 44 | ): 45 | self._p: QPixmap = p 46 | self._scene: QGraphicsScene = QGraphicsScene() 47 | self._item: QGraphicsPixmapItem = self._scene.addPixmap(self._p) 48 | self.setScene(self._scene) 49 | self.fitInView(self._item, self.__aspectRatioMode) 50 | 51 | def setAspectRatioMode( 52 | self, 53 | mode: Qt.AspectRatioMode, 54 | ): 55 | self.__aspectRatioMode = mode 56 | 57 | def resizeEvent( 58 | self, 59 | event: QResizeEvent, 60 | ): 61 | if self._item: 62 | self.fitInView(self._item, self.__aspectRatioMode) 63 | return super().resizeEvent(event) 64 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/notifier.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy import QtGui 6 | from qtpy.QtCore import QPropertyAnimation, QTimer, Qt, Signal 7 | from qtpy.QtGui import QIcon 8 | from qtpy.QtWidgets import QApplication, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget 9 | 10 | from pyqt_openai import ICON_CLOSE, NOTIFIER_MAX_CHAR 11 | 12 | if TYPE_CHECKING: 13 | from qtpy.QtGui import QMouseEvent 14 | 15 | 16 | class NotifierWidget(QWidget): 17 | doubleClicked = Signal() 18 | 19 | def __init__( 20 | self, 21 | informative_text: str = "", 22 | detailed_text: str = "", 23 | parent: QWidget | None = None, 24 | ): 25 | super().__init__(parent) 26 | self.__timerVal: int = 10000 27 | self.__initUi(informative_text, detailed_text) 28 | self.__repositionWidget() 29 | 30 | def __initUi( 31 | self, 32 | informative_text: str = "", 33 | detailed_text: str = "", 34 | ): 35 | self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint | Qt.WindowType.SubWindow) 36 | 37 | self.__informativeTextLabel = QLabel(informative_text) if informative_text else QLabel("Informative") 38 | self.__detailedTextLabel = QLabel(detailed_text) if detailed_text else QLabel("Detailed") 39 | self.__detailedTextLabel.setText(self.__detailedTextLabel.text()[:NOTIFIER_MAX_CHAR] + "...") 40 | self.__detailedTextLabel.setWordWrap(True) 41 | 42 | closeBtn = QPushButton() 43 | closeBtn.clicked.connect(self.close) 44 | closeBtn.setIcon(QIcon(ICON_CLOSE)) 45 | 46 | lay = QHBoxLayout() 47 | lay.setContentsMargins(0, 0, 0, 0) 48 | 49 | self.__btnWidget = QWidget() 50 | self.__btnWidget.setLayout(lay) 51 | 52 | lay = QHBoxLayout() 53 | lay.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight) 54 | lay.addWidget(closeBtn) 55 | lay.setContentsMargins(0, 0, 0, 0) 56 | 57 | customMenuBar = QWidget() 58 | customMenuBar.setLayout(lay) 59 | 60 | lay = QVBoxLayout() 61 | lay.addWidget(customMenuBar) 62 | lay.addWidget(self.__informativeTextLabel) 63 | lay.addWidget(self.__detailedTextLabel) 64 | lay.addWidget(self.__btnWidget) 65 | lay.setContentsMargins(0, 0, 0, 0) 66 | 67 | lay.setContentsMargins(8, 8, 8, 8) 68 | self.setLayout(lay) 69 | self.adjustSize() # Adjust size after setting the layout 70 | 71 | def __repositionWidget(self): 72 | ag = QtGui.QGuiApplication.primaryScreen().availableGeometry() 73 | 74 | # Move to bottom right corner 75 | bottom_right_x = ag.width() - self.width() 76 | bottom_right_y = ag.height() - self.height() 77 | self.move(bottom_right_x, bottom_right_y) 78 | 79 | def keyPressEvent(self, event): 80 | if event.key() == Qt.Key.Key_Escape: 81 | self.close() 82 | 83 | return super().keyPressEvent(event) 84 | 85 | def addWidgets( 86 | self, 87 | widgets: list[QWidget], 88 | ): 89 | for widget in widgets: 90 | self.__btnWidget.layout().addWidget(widget) 91 | self.adjustSize() # Adjust size after adding widgets 92 | self.__repositionWidget() # Reposition widget after size adjustment 93 | 94 | def show(self) -> None: 95 | super().show() 96 | self.adjustSize() # Adjust size when showing the widget 97 | self.__repositionWidget() # Reposition widget when showing 98 | QApplication.beep() 99 | self.__timer = QTimer(self) 100 | self.__timer.timeout.connect(self.__checkTimer) 101 | self.__timer.start(1000) 102 | 103 | def __checkTimer(self): 104 | self.__timerVal -= 1000 105 | if self.__timerVal == 1000: 106 | self.__showAnimation() 107 | elif self.__timerVal <= 0: 108 | self.close() 109 | 110 | def __showAnimation(self): 111 | self.__animation: QPropertyAnimation = QPropertyAnimation(self, b"windowOpacity") 112 | self.__animation.finished.connect(self.close) 113 | self.__animation.setDuration(1000) 114 | self.__animation.setStartValue(1.0) 115 | self.__animation.setEndValue(0.0) 116 | self.__animation.start() 117 | 118 | def mouseDoubleClickEvent(self, event: QMouseEvent): 119 | self.doubleClicked.emit() 120 | self.close() 121 | return super().mouseDoubleClickEvent(event) 122 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/questionTooltipLabel.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtGui import QPixmap 6 | 7 | from pyqt_openai import ICON_QUESTION 8 | from pyqt_openai.widgets.svgLabel import SvgLabel 9 | 10 | if TYPE_CHECKING: 11 | from qtpy.QtWidgets import QWidget 12 | 13 | 14 | class QuestionTooltipLabel(SvgLabel): 15 | def __init__( 16 | self, 17 | tooltip: str = "Click for more information", 18 | parent: QWidget | None = None, 19 | ): 20 | super().__init__(parent) 21 | self.setPixmap(QPixmap(ICON_QUESTION)) 22 | self.setToolTip(tooltip) 23 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/randomImagePromptGeneratorWidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Qt 4 | from qtpy.QtWidgets import QCheckBox, QGroupBox, QLabel, QListWidgetItem, QVBoxLayout, QWidget 5 | 6 | from pyqt_openai import RANDOMIZING_PROMPT_SOURCE_ARR 7 | from pyqt_openai.lang.translations import LangClass 8 | from pyqt_openai.widgets.checkBoxListWidget import CheckBoxListWidget 9 | 10 | 11 | class RandomImagePromptGeneratorWidget(QWidget): 12 | def __init__( 13 | self, 14 | parent: QWidget | None = None, 15 | ): 16 | super().__init__(parent) 17 | self.__initUi() 18 | 19 | def __initUi(self): 20 | # TODO LANGUAGE 21 | self.setWindowTitle("Random Sentence Generator") 22 | 23 | lbl = QLabel("Select the elements you want to include in the prompt") 24 | 25 | self.__allCheckBox: QCheckBox = QCheckBox(LangClass.TRANSLATIONS["Select All"]) 26 | 27 | self.__listWidget: CheckBoxListWidget = CheckBoxListWidget() 28 | 29 | for e in RANDOMIZING_PROMPT_SOURCE_ARR: 30 | item = QListWidgetItem(", ".join(e)) 31 | item.setFlags( 32 | item.flags() 33 | | Qt.ItemFlag.ItemIsUserCheckable 34 | | Qt.ItemFlag.ItemIsEditable, 35 | ) 36 | item.setCheckState(Qt.CheckState.Unchecked) 37 | self.__listWidget.addItem(item) 38 | 39 | lay = QVBoxLayout() 40 | lay.addWidget(lbl) 41 | lay.addWidget(self.__allCheckBox) 42 | lay.addWidget(self.__listWidget) 43 | 44 | self.__randomPromptGroup: QGroupBox = QGroupBox() 45 | self.__randomPromptGroup.setTitle("List of random words to generate a prompt") 46 | self.__randomPromptGroup.setLayout(lay) 47 | 48 | useBtn = QCheckBox("Use random-generated prompt") 49 | useBtn.toggled.connect(self.__toggleRandomPrompt) 50 | 51 | lay = QVBoxLayout() 52 | lay.addWidget(useBtn) 53 | lay.addWidget(self.__randomPromptGroup) 54 | lay.setContentsMargins(0, 0, 0, 0) 55 | 56 | self.setLayout(lay) 57 | 58 | self.__allCheckBox.stateChanged.connect( 59 | self.__listWidget.toggleState, 60 | ) # if allChkBox is checked, tablewidget checkboxes will also be checked 61 | self.__randomPromptGroup.setVisible(False) 62 | 63 | def isRandomPromptEnabled(self): 64 | return self.__randomPromptGroup.isVisible() 65 | 66 | def __toggleRandomPrompt(self, f: bool): 67 | self.__randomPromptGroup.setVisible(f) 68 | self.__allCheckBox.setChecked(f) 69 | 70 | def getRandomPromptSourceArr(self) -> list[list[str]] | None: 71 | return ( 72 | [t.split(", ") for t in self.__listWidget.getCheckedItemsText()] 73 | if self.isRandomPromptEnabled() 74 | else None 75 | ) 76 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/readme.txt: -------------------------------------------------------------------------------- 1 | This directory contains widget scripts which are used commonly in the app. -------------------------------------------------------------------------------- /pyqt_openai/widgets/scrollableErrorDialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtWidgets import QDialog, QHBoxLayout, QLabel, QPushButton, QScrollArea, QVBoxLayout 6 | 7 | from pyqt_openai import REPORT_ERROR_URL 8 | from pyqt_openai.lang.translations import LangClass 9 | from pyqt_openai.widgets.linkLabel import LinkLabel 10 | 11 | if TYPE_CHECKING: 12 | from qtpy.QtWidgets import QWidget 13 | 14 | 15 | class ScrollableErrorDialog(QDialog): 16 | def __init__( 17 | self, 18 | error_msg: str, 19 | parent: QWidget | None = None, 20 | ): 21 | super().__init__(parent) 22 | self.__initUi(error_msg) 23 | 24 | def __initUi( 25 | self, 26 | error_msg: str, 27 | ): 28 | # TODO LANGUAGE 29 | self.setWindowTitle(LangClass.TRANSLATIONS["Error"]) 30 | 31 | # Main layout 32 | main_layout = QVBoxLayout(self) 33 | 34 | # Add a label to display the critical error title 35 | label = QLabel("An unexpected error occurred.") 36 | main_layout.addWidget(label) 37 | 38 | # Scroll Area to hold the error message 39 | scroll_area = QScrollArea(self) 40 | scroll_area.setWidgetResizable(True) 41 | 42 | # Error message label inside the scroll area 43 | error_label = QLabel(error_msg) 44 | error_label.setWordWrap(True) 45 | 46 | # Set the error message into the scroll area 47 | scroll_area.setWidget(error_label) 48 | 49 | # Add the scroll area to the main layout 50 | main_layout.addWidget(scroll_area) 51 | 52 | # Button layout for the "OK" button 53 | button_layout = QHBoxLayout() 54 | report_error_lbl = LinkLabel() 55 | report_error_lbl.setUrl(REPORT_ERROR_URL) 56 | # TODO LANGUAGE 57 | report_error_lbl.setText("Report Error") 58 | 59 | ok_button = QPushButton(LangClass.TRANSLATIONS["OK"]) 60 | ok_button.clicked.connect(self.accept) # Close the dialog when OK is clicked 61 | button_layout.addWidget(report_error_lbl) 62 | button_layout.addStretch(1) # To push the button to the right 63 | button_layout.addWidget(ok_button) 64 | 65 | # Add the button layout to the main layout 66 | main_layout.addLayout(button_layout) 67 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/searchBar.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from qtpy.QtCore import Signal 4 | from qtpy.QtWidgets import QApplication, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QWidget 5 | 6 | from pyqt_openai import ICON_SEARCH 7 | from pyqt_openai.widgets.svgLabel import SvgLabel 8 | 9 | 10 | class SearchBar(QWidget): 11 | searched = Signal(str) 12 | 13 | def __init__( 14 | self, 15 | parent: QWidget | None = None, 16 | ): 17 | super().__init__(parent) 18 | # search bar label 19 | self.__label: QLabel = QLabel() 20 | 21 | self._initUi() 22 | 23 | def _initUi(self): 24 | self.__searchLineEdit: QLineEdit = QLineEdit() 25 | self.__searchIconLbl: SvgLabel = SvgLabel() 26 | ps = QApplication.font().pointSize() 27 | self.__searchIconLbl.setFixedSize(ps, ps) 28 | 29 | self.__searchBar: QWidget = QWidget() 30 | self.__searchBar.setObjectName("searchBar") 31 | 32 | lay = QHBoxLayout() 33 | lay.addWidget(self.__searchIconLbl) 34 | lay.addWidget(self.__searchLineEdit) 35 | self.__searchBar.setLayout(lay) 36 | lay.setContentsMargins(ps // 2, 0, 0, 0) 37 | lay.setSpacing(0) 38 | 39 | self.__searchLineEdit.setFocus() 40 | self.__searchLineEdit.textChanged.connect(self.__searched) 41 | 42 | self.setAutoFillBackground(True) 43 | 44 | lay = QHBoxLayout() 45 | lay.addWidget(self.__searchBar) 46 | lay.setContentsMargins(0, 0, 0, 0) 47 | lay.setSpacing(2) 48 | 49 | self._topWidget: QWidget = QWidget() 50 | self._topWidget.setLayout(lay) 51 | 52 | lay = QGridLayout() 53 | lay.addWidget(self._topWidget) 54 | 55 | searchWidget: QWidget = QWidget() 56 | searchWidget.setLayout(lay) 57 | lay.setContentsMargins(0, 0, 0, 0) 58 | 59 | lay = QGridLayout() 60 | lay.addWidget(searchWidget) 61 | lay.setContentsMargins(0, 0, 0, 0) 62 | 63 | self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Preferred) 64 | 65 | self.__setStyle() 66 | 67 | self.setLayout(lay) 68 | 69 | # ex) searchBar.setLabel(True, 'Search Text') 70 | def setLabel( 71 | self, 72 | visibility: bool = True, 73 | text: str | None = None, 74 | ): 75 | if text: 76 | self.__label.setText(text) 77 | self.__label.setVisible(visibility) 78 | 79 | def __setStyle(self): 80 | self.__searchIconLbl.setSvgFile(ICON_SEARCH) 81 | self.setStyleSheet( 82 | """ 83 | QLineEdit 84 | { 85 | background: transparent; 86 | border: none; 87 | } 88 | QWidget#searchBar 89 | { 90 | border: 1px solid gray; 91 | } 92 | QWidget { padding: 5px; } 93 | """, 94 | ) 95 | 96 | def __searched(self, text: str): 97 | self.searched.emit(text) 98 | 99 | def setSearchIcon( 100 | self, 101 | icon_filename: str, 102 | ): 103 | self.__searchIconLbl.setSvgFile(icon_filename) 104 | 105 | def setPlaceHolder( 106 | self, 107 | text: str, 108 | ): 109 | self.__searchLineEdit.setPlaceholderText(text) 110 | 111 | def getSearchBar(self) -> QLineEdit: 112 | return self.__searchLineEdit 113 | 114 | def getSearchLabel(self) -> SvgLabel: 115 | return self.__searchIconLbl 116 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/showingKeyUserInputLineEdit.py: -------------------------------------------------------------------------------- 1 | # TODO WILL_BE_USED AFTER v2.x.0 2 | 3 | # This works perfectly for showing the key combination in a QLineEdit widget. 4 | from __future__ import annotations 5 | 6 | from typing import TYPE_CHECKING 7 | 8 | from qtpy.QtCore import Qt 9 | from qtpy.QtWidgets import QLineEdit 10 | 11 | if TYPE_CHECKING: 12 | from qtpy.QtGui import QKeyEvent 13 | from qtpy.QtWidgets import QWidget 14 | 15 | 16 | class ShowingKeyUserInputLineEdit(QLineEdit): 17 | def __init__( 18 | self, 19 | parent: QWidget | None = None, 20 | ): 21 | super().__init__(parent) 22 | self.shortcut: str = "" 23 | 24 | def keyPressEvent( 25 | self, 26 | event: QKeyEvent, 27 | ): 28 | modifiers: Qt.KeyboardModifier = event.modifiers() 29 | key: Qt.Key | int = event.key() 30 | 31 | key_text: str | None = self._get_key_text(key) 32 | if key_text: 33 | parts: list[str] = [] 34 | 35 | if modifiers & Qt.KeyboardModifier.ControlModifier: 36 | parts.append("Ctrl") 37 | if modifiers & Qt.KeyboardModifier.AltModifier: 38 | parts.append("Alt") 39 | if modifiers & Qt.KeyboardModifier.ShiftModifier: 40 | parts.append("Shift") 41 | if modifiers & Qt.KeyboardModifier.MetaModifier: 42 | parts.append("Meta") 43 | 44 | parts.append(key_text) 45 | self.shortcut = "+".join(parts) 46 | self.setText(self.shortcut) 47 | 48 | def _get_key_text( 49 | self, 50 | key: Qt.Key | int, 51 | ) -> str | None: 52 | special_keys: dict[Qt.Key, str] = { 53 | Qt.Key.Key_Escape: "Esc", 54 | Qt.Key.Key_Tab: "Tab", 55 | Qt.Key.Key_Backtab: "Backtab", 56 | Qt.Key.Key_Backspace: "Backspace", 57 | Qt.Key.Key_Return: "Return", 58 | Qt.Key.Key_Enter: "Enter", 59 | Qt.Key.Key_Insert: "Ins", 60 | Qt.Key.Key_Delete: "Del", 61 | Qt.Key.Key_Pause: "Pause", 62 | Qt.Key.Key_Print: "Print", 63 | Qt.Key.Key_SysReq: "SysReq", 64 | Qt.Key.Key_Clear: "Clear", 65 | Qt.Key.Key_Home: "Home", 66 | Qt.Key.Key_End: "End", 67 | Qt.Key.Key_Left: "Left", 68 | Qt.Key.Key_Up: "Up", 69 | Qt.Key.Key_Right: "Right", 70 | Qt.Key.Key_Down: "Down", 71 | Qt.Key.Key_PageUp: "PageUp", 72 | Qt.Key.Key_PageDown: "PageDown", 73 | Qt.Key.Key_Space: "Space", 74 | Qt.Key.Key_CapsLock: "CapsLock", 75 | Qt.Key.Key_NumLock: "NumLock", 76 | Qt.Key.Key_ScrollLock: "ScrollLock", 77 | Qt.Key.Key_Menu: "Menu", 78 | Qt.Key.Key_Help: "Help", 79 | } 80 | 81 | if key in special_keys: 82 | return special_keys[key] 83 | if 0x20 <= key <= 0x7E: # Regular printable ASCII characters 84 | return chr(key) 85 | if 0x100 <= key <= 0x10FFFF: # Other valid Unicode characters 86 | try: 87 | return chr(key).upper() 88 | except ValueError: 89 | pass 90 | return None 91 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/svgLabel.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtGui import QPainter 6 | from qtpy.QtSvg import QSvgRenderer 7 | from qtpy.QtWidgets import QLabel 8 | 9 | if TYPE_CHECKING: 10 | from qtpy.QtGui import QPaintEvent 11 | from qtpy.QtWidgets import QWidget 12 | 13 | 14 | class SvgLabel(QLabel): 15 | def __init__( 16 | self, 17 | parent: QWidget | None = None, 18 | ): 19 | super().__init__(parent) 20 | self.__renderer: QSvgRenderer | None = None 21 | 22 | def paintEvent( 23 | self, 24 | event: QPaintEvent, 25 | ): 26 | painter = QPainter(self) 27 | if self.__renderer: 28 | self.__renderer.render(painter) 29 | return super().paintEvent(event) 30 | 31 | def setSvgFile( 32 | self, 33 | filename: str, 34 | ): 35 | self.__renderer = QSvgRenderer(filename) 36 | self.resize(self.__renderer.defaultSize()) 37 | length = max(self.sizeHint().width(), self.sizeHint().height()) 38 | self.setFixedSize(length, length) 39 | -------------------------------------------------------------------------------- /pyqt_openai/widgets/toolButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from qtpy.QtGui import QColor, QIcon 6 | from qtpy.QtWidgets import QGraphicsColorizeEffect, QToolButton 7 | 8 | from pyqt_openai.util.button_style_helper import ButtonStyleHelper 9 | 10 | if TYPE_CHECKING: 11 | from qtpy.QtCore import QEvent, QObject 12 | from qtpy.QtWidgets import QWidget 13 | 14 | 15 | class ToolButton(QToolButton): 16 | def __init__( 17 | self, 18 | base_widget: QWidget | None = None, 19 | *args, 20 | **kwargs, 21 | ): 22 | super().__init__(*args, **kwargs) 23 | self.style_helper: ButtonStyleHelper = ButtonStyleHelper(base_widget) 24 | self.setStyleSheet(self.style_helper.styleInit()) 25 | self.installEventFilter(self) 26 | 27 | def setStyleAndIcon( 28 | self, 29 | icon: str, 30 | ): 31 | self.style_helper.__icon = icon 32 | self.setStyleSheet(self.style_helper.styleInit()) 33 | self.setIcon(QIcon(icon)) 34 | 35 | def eventFilter( 36 | self, 37 | obj: QObject, 38 | event: QEvent, 39 | ) -> bool: 40 | if obj == self: 41 | if event.type() == 98: # Event type for EnableChange 42 | effect = QGraphicsColorizeEffect() 43 | effect.setColor(QColor(255, 255, 255)) 44 | if self.isEnabled(): 45 | effect.setStrength(0) 46 | else: 47 | effect.setStrength(1) 48 | effect.setColor(QColor(150, 150, 150)) 49 | self.setGraphicsEffect(effect) 50 | return super().eventFilter(obj, event) 51 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PySide6 2 | pyperclip 3 | jinja2 4 | requests 5 | pyaudio 6 | pillow 7 | psutil 8 | filetype 9 | 10 | openai 11 | anthropic 12 | google-generativeai 13 | replicate 14 | 15 | llama-index 16 | docx2txt 17 | openpyxl 18 | 19 | g4f 20 | nodriver 21 | 22 | curl_cffi 23 | litellm 24 | 25 | edge-tts 26 | qtpy -------------------------------------------------------------------------------- /version_info.txt: -------------------------------------------------------------------------------- 1 | # UTF-8 2 | # 3 | # For information on the syntax of this file, see: 4 | # http://www.pyinstaller.org/ 5 | # 6 | VSVersionInfo( 7 | ffi=FixedFileInfo( 8 | filevers=(1, 9, 0), 9 | prodvers=(1, 9, 0), 10 | mask=0x3f, 11 | flags=0x0, 12 | OS=0x4, 13 | fileType=0x1, 14 | subtype=0x0, 15 | date=(0, 0) 16 | ), 17 | kids=[ 18 | StringFileInfo( 19 | [ 20 | StringTable( 21 | u'040904B0', 22 | [StringStruct(u'FileVersion', u'1.9.0'), 23 | StringStruct(u'ProductName', u'VividNode'), 24 | StringStruct(u'LegalCopyright', u'Copyright © 2024 Jung Gyu Yoon'), 25 | StringStruct(u'ProductVersion', u'1.9.0')]) 26 | ]), 27 | VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) 28 | ] 29 | ) 30 | --------------------------------------------------------------------------------