├── .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 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/favorite_yes.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/file.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/focus_mode.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/history.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/import.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pyqt_openai/ico/refresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------