pygpt-net 2.6.21__py3-none-any.whl → 2.6.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +3 -1
- pygpt_net/controller/__init__.py +4 -8
- pygpt_net/controller/access/voice.py +2 -2
- pygpt_net/controller/agent/llama.py +3 -0
- pygpt_net/controller/assistant/batch.py +2 -3
- pygpt_net/controller/assistant/editor.py +2 -2
- pygpt_net/controller/assistant/files.py +2 -3
- pygpt_net/controller/assistant/store.py +2 -2
- pygpt_net/controller/audio/audio.py +2 -2
- pygpt_net/controller/chat/response.py +4 -0
- pygpt_net/controller/ctx/ctx.py +2 -1
- pygpt_net/controller/files/files.py +24 -55
- pygpt_net/controller/idx/indexer.py +85 -76
- pygpt_net/controller/lang/lang.py +52 -34
- pygpt_net/controller/model/importer.py +2 -2
- pygpt_net/controller/notepad/notepad.py +86 -84
- pygpt_net/controller/plugins/settings.py +3 -4
- pygpt_net/controller/settings/profile.py +105 -124
- pygpt_net/controller/theme/menu.py +154 -57
- pygpt_net/controller/theme/nodes.py +51 -44
- pygpt_net/controller/theme/theme.py +33 -9
- pygpt_net/controller/tools/tools.py +2 -2
- pygpt_net/controller/ui/tabs.py +2 -3
- pygpt_net/core/agents/observer/evaluation.py +2 -2
- pygpt_net/core/agents/runners/loop.py +1 -0
- pygpt_net/core/bridge/bridge.py +2 -0
- pygpt_net/core/ctx/container.py +13 -12
- pygpt_net/core/ctx/output.py +7 -4
- pygpt_net/core/debug/console/console.py +2 -2
- pygpt_net/core/filesystem/actions.py +1 -2
- pygpt_net/core/filesystem/opener.py +261 -0
- pygpt_net/core/filesystem/url.py +13 -10
- pygpt_net/core/platforms/platforms.py +5 -4
- pygpt_net/core/render/plain/helpers.py +2 -5
- pygpt_net/core/render/plain/renderer.py +26 -30
- pygpt_net/core/render/web/body.py +1 -1
- pygpt_net/core/settings/settings.py +43 -13
- pygpt_net/core/tabs/tabs.py +20 -13
- pygpt_net/data/config/config.json +4 -4
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/web-blocks.dark.css +7 -1
- pygpt_net/data/css/web-blocks.light.css +5 -2
- pygpt_net/data/css/web-chatgpt.dark.css +7 -1
- pygpt_net/data/css/web-chatgpt.light.css +3 -0
- pygpt_net/data/css/web-chatgpt_wide.dark.css +7 -1
- pygpt_net/data/css/web-chatgpt_wide.light.css +3 -0
- pygpt_net/data/locale/locale.de.ini +5 -1
- pygpt_net/data/locale/locale.en.ini +5 -1
- pygpt_net/data/locale/locale.es.ini +5 -1
- pygpt_net/data/locale/locale.fr.ini +5 -1
- pygpt_net/data/locale/locale.it.ini +5 -1
- pygpt_net/data/locale/locale.pl.ini +6 -4
- pygpt_net/data/locale/locale.uk.ini +5 -1
- pygpt_net/data/locale/locale.zh.ini +5 -1
- pygpt_net/plugin/twitter/plugin.py +2 -2
- pygpt_net/provider/core/config/patch.py +12 -1
- pygpt_net/tools/audio_transcriber/ui/dialogs.py +44 -54
- pygpt_net/tools/code_interpreter/body.py +1 -2
- pygpt_net/tools/code_interpreter/tool.py +7 -4
- pygpt_net/tools/code_interpreter/ui/html.py +1 -3
- pygpt_net/tools/code_interpreter/ui/widgets.py +2 -3
- pygpt_net/tools/html_canvas/ui/widgets.py +1 -3
- pygpt_net/tools/image_viewer/ui/dialogs.py +40 -37
- pygpt_net/tools/indexer/ui/widgets.py +2 -4
- pygpt_net/tools/media_player/tool.py +2 -5
- pygpt_net/tools/media_player/ui/widgets.py +60 -36
- pygpt_net/tools/text_editor/ui/widgets.py +18 -19
- pygpt_net/tools/translator/ui/widgets.py +39 -35
- pygpt_net/ui/base/context_menu.py +9 -4
- pygpt_net/ui/dialog/db.py +1 -3
- pygpt_net/ui/dialog/models.py +1 -3
- pygpt_net/ui/dialog/models_importer.py +2 -4
- pygpt_net/ui/dialogs.py +34 -30
- pygpt_net/ui/layout/chat/attachments.py +72 -84
- pygpt_net/ui/layout/chat/attachments_ctx.py +40 -44
- pygpt_net/ui/layout/chat/attachments_uploaded.py +36 -39
- pygpt_net/ui/layout/chat/calendar.py +100 -70
- pygpt_net/ui/layout/chat/chat.py +23 -17
- pygpt_net/ui/layout/chat/input.py +95 -118
- pygpt_net/ui/layout/chat/output.py +100 -162
- pygpt_net/ui/layout/chat/painter.py +89 -61
- pygpt_net/ui/layout/ctx/ctx_list.py +43 -52
- pygpt_net/ui/layout/status.py +23 -14
- pygpt_net/ui/layout/toolbox/agent.py +27 -38
- pygpt_net/ui/layout/toolbox/agent_llama.py +41 -45
- pygpt_net/ui/layout/toolbox/assistants.py +42 -38
- pygpt_net/ui/layout/toolbox/computer_env.py +32 -23
- pygpt_net/ui/layout/toolbox/footer.py +13 -16
- pygpt_net/ui/layout/toolbox/image.py +18 -21
- pygpt_net/ui/layout/toolbox/indexes.py +46 -89
- pygpt_net/ui/layout/toolbox/mode.py +20 -7
- pygpt_net/ui/layout/toolbox/model.py +12 -10
- pygpt_net/ui/layout/toolbox/presets.py +68 -52
- pygpt_net/ui/layout/toolbox/prompt.py +31 -58
- pygpt_net/ui/layout/toolbox/toolbox.py +25 -21
- pygpt_net/ui/layout/toolbox/vision.py +20 -22
- pygpt_net/ui/main.py +2 -4
- pygpt_net/ui/menu/about.py +64 -84
- pygpt_net/ui/menu/audio.py +87 -63
- pygpt_net/ui/menu/config.py +121 -127
- pygpt_net/ui/menu/debug.py +69 -76
- pygpt_net/ui/menu/file.py +32 -35
- pygpt_net/ui/menu/menu.py +2 -3
- pygpt_net/ui/menu/plugins.py +69 -33
- pygpt_net/ui/menu/theme.py +45 -46
- pygpt_net/ui/menu/tools.py +56 -60
- pygpt_net/ui/menu/video.py +20 -25
- pygpt_net/ui/tray.py +1 -2
- pygpt_net/ui/widget/audio/bar.py +1 -3
- pygpt_net/ui/widget/audio/input_button.py +3 -4
- pygpt_net/ui/widget/calendar/select.py +1 -2
- pygpt_net/ui/widget/dialog/base.py +12 -9
- pygpt_net/ui/widget/dialog/editor_file.py +20 -23
- pygpt_net/ui/widget/dialog/find.py +25 -24
- pygpt_net/ui/widget/dialog/profile.py +57 -53
- pygpt_net/ui/widget/draw/painter.py +62 -93
- pygpt_net/ui/widget/element/button.py +42 -30
- pygpt_net/ui/widget/element/checkbox.py +23 -15
- pygpt_net/ui/widget/element/group.py +6 -5
- pygpt_net/ui/widget/element/labels.py +1 -2
- pygpt_net/ui/widget/filesystem/explorer.py +93 -102
- pygpt_net/ui/widget/image/display.py +1 -2
- pygpt_net/ui/widget/lists/assistant.py +1 -2
- pygpt_net/ui/widget/lists/attachment.py +1 -2
- pygpt_net/ui/widget/lists/attachment_ctx.py +1 -2
- pygpt_net/ui/widget/lists/context.py +2 -4
- pygpt_net/ui/widget/lists/index.py +1 -2
- pygpt_net/ui/widget/lists/model.py +1 -2
- pygpt_net/ui/widget/lists/model_editor.py +1 -2
- pygpt_net/ui/widget/lists/model_importer.py +1 -2
- pygpt_net/ui/widget/lists/preset.py +1 -2
- pygpt_net/ui/widget/lists/preset_plugins.py +1 -2
- pygpt_net/ui/widget/lists/profile.py +1 -2
- pygpt_net/ui/widget/lists/uploaded.py +1 -2
- pygpt_net/ui/widget/option/checkbox.py +2 -4
- pygpt_net/ui/widget/option/checkbox_list.py +1 -4
- pygpt_net/ui/widget/option/cmd.py +1 -4
- pygpt_net/ui/widget/option/dictionary.py +25 -28
- pygpt_net/ui/widget/option/input.py +1 -3
- pygpt_net/ui/widget/tabs/Input.py +16 -12
- pygpt_net/ui/widget/tabs/body.py +5 -3
- pygpt_net/ui/widget/tabs/layout.py +41 -28
- pygpt_net/ui/widget/tabs/output.py +442 -85
- pygpt_net/ui/widget/textarea/calendar_note.py +1 -2
- pygpt_net/ui/widget/textarea/editor.py +41 -73
- pygpt_net/ui/widget/textarea/find.py +11 -10
- pygpt_net/ui/widget/textarea/html.py +3 -6
- pygpt_net/ui/widget/textarea/input.py +134 -69
- pygpt_net/ui/widget/textarea/notepad.py +54 -38
- pygpt_net/ui/widget/textarea/output.py +65 -54
- pygpt_net/ui/widget/textarea/search_input.py +5 -4
- pygpt_net/ui/widget/textarea/web.py +2 -4
- pygpt_net/ui/widget/vision/camera.py +2 -31
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/METADATA +38 -174
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/RECORD +160 -159
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/entry_points.txt +0 -0
|
@@ -6,27 +6,32 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt
|
|
13
|
-
from PySide6.QtGui import QAction, QIcon, QKeySequence,
|
|
13
|
+
from PySide6.QtGui import QAction, QIcon, QKeySequence, QFontMetrics
|
|
14
14
|
from PySide6.QtWidgets import QTextEdit
|
|
15
15
|
|
|
16
16
|
from pygpt_net.core.text.finder import Finder
|
|
17
17
|
from pygpt_net.utils import trans
|
|
18
18
|
|
|
19
|
-
import pygpt_net.icons_rc
|
|
20
|
-
|
|
21
19
|
|
|
22
20
|
class BaseCodeEditor(QTextEdit):
|
|
21
|
+
|
|
22
|
+
_ICON_VOLUME = QIcon(":/icons/volume.svg")
|
|
23
|
+
_ICON_SAVE = QIcon(":/icons/save.svg")
|
|
24
|
+
_ICON_SEARCH = QIcon(":/icons/search.svg")
|
|
25
|
+
_ICON_CLOSE = QIcon(":/icons/close.svg")
|
|
26
|
+
_FIND_SEQ = QKeySequence("Ctrl+F")
|
|
27
|
+
|
|
23
28
|
def __init__(self, window=None):
|
|
24
29
|
"""
|
|
25
30
|
Base code editor
|
|
26
31
|
|
|
27
32
|
:param window: main window
|
|
28
33
|
"""
|
|
29
|
-
super(
|
|
34
|
+
super().__init__(window)
|
|
30
35
|
self.window = window
|
|
31
36
|
self.finder = Finder(window, self)
|
|
32
37
|
self.setReadOnly(True)
|
|
@@ -40,114 +45,89 @@ class BaseCodeEditor(QTextEdit):
|
|
|
40
45
|
self.excluded_copy_to = []
|
|
41
46
|
self.textChanged.connect(self.text_changed)
|
|
42
47
|
|
|
43
|
-
# tabulation
|
|
44
48
|
metrics = QFontMetrics(self.font())
|
|
45
49
|
space_width = metrics.horizontalAdvance(" ")
|
|
46
50
|
self.setTabStopDistance(4 * space_width)
|
|
47
51
|
|
|
48
52
|
def text_changed(self):
|
|
49
|
-
"""On text changed"""
|
|
50
53
|
self.finder.text_changed()
|
|
51
54
|
|
|
52
55
|
def update_stylesheet(self, data: str):
|
|
53
|
-
"""
|
|
54
|
-
Update stylesheet
|
|
55
|
-
|
|
56
|
-
:param data: stylesheet CSS
|
|
57
|
-
"""
|
|
58
56
|
self.setStyleSheet(self.default_stylesheet + data)
|
|
59
57
|
|
|
60
58
|
def contextMenuEvent(self, event):
|
|
61
|
-
"""
|
|
62
|
-
Context menu event
|
|
63
|
-
|
|
64
|
-
:param event: Event
|
|
65
|
-
"""
|
|
66
59
|
menu = self.createStandardContextMenu()
|
|
67
|
-
|
|
60
|
+
cursor = self.textCursor()
|
|
61
|
+
selected_text = cursor.selectedText()
|
|
68
62
|
|
|
69
63
|
if selected_text:
|
|
70
|
-
|
|
71
|
-
plain_text = self.textCursor().selection().toPlainText()
|
|
64
|
+
plain_text = cursor.selection().toPlainText()
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
|
|
66
|
+
action = QAction(self._ICON_VOLUME, trans('text.context_menu.audio.read'), menu)
|
|
75
67
|
action.triggered.connect(self.audio_read_selection)
|
|
76
68
|
menu.addAction(action)
|
|
77
69
|
|
|
78
|
-
# copy to
|
|
79
70
|
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(
|
|
80
|
-
|
|
71
|
+
menu,
|
|
81
72
|
selected_text,
|
|
82
73
|
excluded=self.excluded_copy_to
|
|
83
74
|
)
|
|
75
|
+
try:
|
|
76
|
+
copy_to_menu.setParent(menu)
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
84
79
|
menu.addMenu(copy_to_menu)
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
|
|
81
|
+
action = QAction(self._ICON_SAVE, trans('action.save_selection_as'), menu)
|
|
88
82
|
action.triggered.connect(
|
|
89
83
|
lambda: self.window.controller.chat.common.save_text(plain_text))
|
|
90
84
|
menu.addAction(action)
|
|
91
85
|
else:
|
|
92
|
-
|
|
93
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_as'), self)
|
|
86
|
+
action = QAction(self._ICON_SAVE, trans('action.save_as'), menu)
|
|
94
87
|
action.triggered.connect(
|
|
95
88
|
lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
96
89
|
menu.addAction(action)
|
|
97
90
|
|
|
98
|
-
action = QAction(
|
|
91
|
+
action = QAction(self._ICON_SEARCH, trans('text.context_menu.find'), menu)
|
|
99
92
|
action.triggered.connect(self.find_open)
|
|
100
|
-
action.setShortcut(
|
|
93
|
+
action.setShortcut(self._FIND_SEQ)
|
|
101
94
|
menu.addAction(action)
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
action
|
|
105
|
-
action.triggered.connect(
|
|
106
|
-
lambda: self.clear_content())
|
|
96
|
+
action = QAction(self._ICON_CLOSE, trans('action.clear'), menu)
|
|
97
|
+
action.triggered.connect(self.clear_content)
|
|
107
98
|
menu.addAction(action)
|
|
108
99
|
|
|
109
|
-
menu.
|
|
100
|
+
menu.exec(event.globalPos())
|
|
101
|
+
menu.deleteLater()
|
|
110
102
|
|
|
111
103
|
def clear_content(self):
|
|
112
|
-
|
|
113
|
-
cursor = self.textCursor()
|
|
114
|
-
cursor.select(QTextCursor.Document)
|
|
115
|
-
cursor.removeSelectedText()
|
|
104
|
+
self.clear()
|
|
116
105
|
|
|
117
106
|
def audio_read_selection(self):
|
|
118
|
-
"""Read selected text (audio)"""
|
|
119
107
|
self.window.controller.audio.read_text(self.textCursor().selectedText())
|
|
120
108
|
|
|
121
109
|
def find_open(self):
|
|
122
|
-
"""Open find dialog"""
|
|
123
110
|
self.window.controller.finder.open(self.finder)
|
|
124
111
|
|
|
125
112
|
def on_update(self):
|
|
126
|
-
|
|
127
|
-
self.finder.clear() # clear finder
|
|
113
|
+
self.finder.clear()
|
|
128
114
|
|
|
129
115
|
def on_destroy(self):
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
try:
|
|
117
|
+
self.textChanged.disconnect(self.text_changed)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
self.window.controller.finder.unset(self.finder)
|
|
132
121
|
|
|
133
122
|
def keyPressEvent(self, e):
|
|
134
|
-
"""
|
|
135
|
-
Key press event
|
|
136
|
-
|
|
137
|
-
:param e: Event
|
|
138
|
-
"""
|
|
139
123
|
if e.key() == Qt.Key_F and e.modifiers() & Qt.ControlModifier:
|
|
140
124
|
self.find_open()
|
|
141
125
|
else:
|
|
142
|
-
super(
|
|
126
|
+
super().keyPressEvent(e)
|
|
143
127
|
|
|
144
128
|
def wheelEvent(self, event):
|
|
145
|
-
"""
|
|
146
|
-
Wheel event: set font size
|
|
147
|
-
|
|
148
|
-
:param event: Event
|
|
149
|
-
"""
|
|
150
129
|
if event.modifiers() & Qt.ControlModifier:
|
|
130
|
+
prev = self.value
|
|
151
131
|
if event.angleDelta().y() > 0:
|
|
152
132
|
if self.value < self.max_font_size:
|
|
153
133
|
self.value += 1
|
|
@@ -155,18 +135,14 @@ class BaseCodeEditor(QTextEdit):
|
|
|
155
135
|
if self.value > self.min_font_size:
|
|
156
136
|
self.value -= 1
|
|
157
137
|
|
|
158
|
-
self.
|
|
138
|
+
if self.value != prev:
|
|
139
|
+
self.update_stylesheet(f"QTextEdit {{ font-size: {self.value}px }};")
|
|
159
140
|
event.accept()
|
|
160
141
|
else:
|
|
161
|
-
super(
|
|
142
|
+
super().wheelEvent(event)
|
|
162
143
|
|
|
163
144
|
def focusInEvent(self, e):
|
|
164
|
-
|
|
165
|
-
Focus in event
|
|
166
|
-
|
|
167
|
-
:param e: focus event
|
|
168
|
-
"""
|
|
169
|
-
super(BaseCodeEditor, self).focusInEvent(e)
|
|
145
|
+
super().focusInEvent(e)
|
|
170
146
|
self.window.controller.finder.focus_in(self.finder)
|
|
171
147
|
|
|
172
148
|
|
|
@@ -177,12 +153,4 @@ class CodeEditor(BaseCodeEditor):
|
|
|
177
153
|
|
|
178
154
|
:param window: main window
|
|
179
155
|
"""
|
|
180
|
-
super(
|
|
181
|
-
self.window = window
|
|
182
|
-
self.setReadOnly(True)
|
|
183
|
-
self.value = 12
|
|
184
|
-
self.max_font_size = 42
|
|
185
|
-
self.min_font_size = 8
|
|
186
|
-
self.setProperty('class', 'code-editor')
|
|
187
|
-
self.default_stylesheet = ""
|
|
188
|
-
self.setStyleSheet(self.default_stylesheet)
|
|
156
|
+
super().__init__(window)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6 import QtCore
|
|
@@ -21,13 +21,15 @@ class FindInput(QLineEdit):
|
|
|
21
21
|
:param window: main window
|
|
22
22
|
:param id: info window id
|
|
23
23
|
"""
|
|
24
|
-
super(
|
|
24
|
+
super().__init__(window)
|
|
25
25
|
|
|
26
26
|
self.window = window
|
|
27
27
|
self.id = id
|
|
28
|
-
self.textChanged.connect(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
self.textChanged.connect(self._on_text_changed)
|
|
29
|
+
|
|
30
|
+
@QtCore.Slot(str)
|
|
31
|
+
def _on_text_changed(self, text):
|
|
32
|
+
self.window.controller.finder.search_text_changed(text)
|
|
31
33
|
|
|
32
34
|
def keyPressEvent(self, event):
|
|
33
35
|
"""
|
|
@@ -35,10 +37,10 @@ class FindInput(QLineEdit):
|
|
|
35
37
|
|
|
36
38
|
:param event: key event
|
|
37
39
|
"""
|
|
38
|
-
super(
|
|
40
|
+
super().keyPressEvent(event)
|
|
39
41
|
|
|
40
42
|
# update on Enter
|
|
41
|
-
if event.key()
|
|
43
|
+
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
|
42
44
|
self.window.controller.finder.focus_input(self.text())
|
|
43
45
|
|
|
44
46
|
def focusInEvent(self, e):
|
|
@@ -47,6 +49,5 @@ class FindInput(QLineEdit):
|
|
|
47
49
|
|
|
48
50
|
:param e: focus event
|
|
49
51
|
"""
|
|
50
|
-
super(
|
|
51
|
-
self.window.controller.finder.focus_input(self.text())
|
|
52
|
-
|
|
52
|
+
super().focusInEvent(e)
|
|
53
|
+
self.window.controller.finder.focus_input(self.text())
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.08.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import re
|
|
@@ -15,16 +15,13 @@ from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent
|
|
|
15
15
|
from PySide6.QtWebChannel import QWebChannel
|
|
16
16
|
from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage
|
|
17
17
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
18
|
-
from PySide6.QtGui import QAction, QIcon
|
|
18
|
+
from PySide6.QtGui import QAction, QIcon
|
|
19
19
|
from PySide6.QtWidgets import QMenu
|
|
20
20
|
|
|
21
|
-
from pygpt_net.core.events import RenderEvent
|
|
22
21
|
from pygpt_net.item.ctx import CtxMeta
|
|
23
22
|
from pygpt_net.core.text.web_finder import WebFinder
|
|
24
23
|
from pygpt_net.utils import trans
|
|
25
24
|
|
|
26
|
-
import pygpt_net.icons_rc
|
|
27
|
-
|
|
28
25
|
|
|
29
26
|
class HtmlOutput(QWebEngineView):
|
|
30
27
|
def __init__(self, window=None):
|
|
@@ -142,7 +139,7 @@ class HtmlOutput(QWebEngineView):
|
|
|
142
139
|
menu.addAction(action)
|
|
143
140
|
|
|
144
141
|
# copy to
|
|
145
|
-
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(
|
|
142
|
+
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text)
|
|
146
143
|
menu.addMenu(copy_to_menu)
|
|
147
144
|
|
|
148
145
|
# save as (selected)
|
|
@@ -6,35 +6,53 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.08.25 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from PySide6 import
|
|
13
|
-
from PySide6.QtCore import Qt
|
|
12
|
+
from PySide6.QtCore import Qt, QSize
|
|
14
13
|
from PySide6.QtGui import QAction, QIcon, QImage
|
|
15
|
-
from PySide6.QtWidgets import QTextEdit, QApplication
|
|
14
|
+
from PySide6.QtWidgets import QTextEdit, QApplication, QPushButton
|
|
16
15
|
|
|
17
16
|
from pygpt_net.utils import trans
|
|
18
|
-
import pygpt_net.icons_rc
|
|
19
|
-
|
|
20
17
|
|
|
21
18
|
class ChatInput(QTextEdit):
|
|
19
|
+
|
|
20
|
+
ICON_PASTE = QIcon(":/icons/paste.svg")
|
|
21
|
+
ICON_VOLUME = QIcon(":/icons/volume.svg")
|
|
22
|
+
ICON_SAVE = QIcon(":/icons/save.svg")
|
|
23
|
+
ICON_ATTACHMENT = QIcon(":/icons/add.svg")
|
|
24
|
+
|
|
22
25
|
def __init__(self, window=None):
|
|
23
26
|
"""
|
|
24
27
|
Chat input
|
|
25
28
|
|
|
26
29
|
:param window: main window
|
|
27
30
|
"""
|
|
28
|
-
super(
|
|
31
|
+
super().__init__(window)
|
|
29
32
|
self.window = window
|
|
30
33
|
self.setAcceptRichText(False)
|
|
31
34
|
self.setFocus()
|
|
32
35
|
self.value = self.window.core.config.data['font_size.input']
|
|
33
36
|
self.max_font_size = 42
|
|
34
37
|
self.min_font_size = 8
|
|
38
|
+
self._text_top_padding = 10
|
|
35
39
|
self.textChanged.connect(self.window.controller.ui.update_tokens)
|
|
36
40
|
self.setProperty('class', 'layout-input')
|
|
37
41
|
|
|
42
|
+
# Add a "+" button in the top-left corner to add attachments
|
|
43
|
+
self._init_attachment_button()
|
|
44
|
+
self._apply_text_top_padding()
|
|
45
|
+
|
|
46
|
+
def _apply_text_top_padding(self):
|
|
47
|
+
"""Apply extra top padding inside the text area by using viewport margins."""
|
|
48
|
+
m = self.viewportMargins()
|
|
49
|
+
self.setViewportMargins(m.left(), self._text_top_padding, m.right(), m.bottom())
|
|
50
|
+
|
|
51
|
+
def set_text_top_padding(self, px: int):
|
|
52
|
+
"""Public helper to adjust top padding at runtime."""
|
|
53
|
+
self._text_top_padding = max(0, int(px))
|
|
54
|
+
self._apply_text_top_padding()
|
|
55
|
+
|
|
38
56
|
def insertFromMimeData(self, source):
|
|
39
57
|
"""
|
|
40
58
|
Insert from mime data
|
|
@@ -63,7 +81,8 @@ class ChatInput(QTextEdit):
|
|
|
63
81
|
self.window.controller.attachment.from_clipboard_url(local_path)
|
|
64
82
|
elif source.hasText():
|
|
65
83
|
text = source.text()
|
|
66
|
-
|
|
84
|
+
if text:
|
|
85
|
+
self.window.controller.attachment.from_clipboard_text(text)
|
|
67
86
|
|
|
68
87
|
def contextMenuEvent(self, event):
|
|
69
88
|
"""
|
|
@@ -72,51 +91,45 @@ class ChatInput(QTextEdit):
|
|
|
72
91
|
:param event: event
|
|
73
92
|
"""
|
|
74
93
|
menu = self.createStandardContextMenu()
|
|
75
|
-
|
|
76
|
-
# paste attachment
|
|
77
|
-
if self.window.controller.attachment.clipboard_has_attachment():
|
|
78
|
-
action = QAction(QIcon(":/icons/paste.svg"), trans("action.use.attachment"), self)
|
|
79
|
-
action.triggered.connect(self.action_from_clipboard)
|
|
80
|
-
menu.addAction(action)
|
|
81
|
-
|
|
82
|
-
selected_text = self.textCursor().selectedText()
|
|
83
|
-
if selected_text:
|
|
84
|
-
# plain text
|
|
85
|
-
plain_text = self.textCursor().selection().toPlainText()
|
|
86
|
-
|
|
87
|
-
# audio read
|
|
88
|
-
action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
|
|
89
|
-
action.triggered.connect(self.audio_read_selection)
|
|
90
|
-
menu.addAction(action)
|
|
91
|
-
|
|
92
|
-
# copy to
|
|
93
|
-
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, selected_text, excluded=["input"])
|
|
94
|
-
menu.addMenu(copy_to_menu)
|
|
95
|
-
|
|
96
|
-
# save as (selected)
|
|
97
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
|
|
98
|
-
action.triggered.connect(
|
|
99
|
-
lambda: self.window.controller.chat.common.save_text(plain_text))
|
|
100
|
-
menu.addAction(action)
|
|
101
|
-
else:
|
|
102
|
-
# save as (all)
|
|
103
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_as'), self)
|
|
104
|
-
action.triggered.connect(
|
|
105
|
-
lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
106
|
-
menu.addAction(action)
|
|
107
|
-
|
|
108
94
|
try:
|
|
109
|
-
self.window.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
95
|
+
if self.window.controller.attachment.clipboard_has_attachment():
|
|
96
|
+
action = QAction(self.ICON_PASTE, trans("action.use.attachment"), menu)
|
|
97
|
+
action.triggered.connect(self.action_from_clipboard)
|
|
98
|
+
menu.addAction(action)
|
|
99
|
+
|
|
100
|
+
cursor = self.textCursor()
|
|
101
|
+
selected_text = cursor.selectedText()
|
|
102
|
+
if selected_text:
|
|
103
|
+
plain_text = cursor.selection().toPlainText()
|
|
104
|
+
|
|
105
|
+
action = QAction(self.ICON_VOLUME, trans('text.context_menu.audio.read'), menu)
|
|
106
|
+
action.triggered.connect(self.audio_read_selection)
|
|
107
|
+
menu.addAction(action)
|
|
108
|
+
|
|
109
|
+
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text, excluded=["input"])
|
|
110
|
+
menu.addMenu(copy_to_menu)
|
|
111
|
+
|
|
112
|
+
action = QAction(self.ICON_SAVE, trans('action.save_selection_as'), menu)
|
|
113
|
+
action.triggered.connect(lambda: self.window.controller.chat.common.save_text(plain_text))
|
|
114
|
+
menu.addAction(action)
|
|
115
|
+
else:
|
|
116
|
+
action = QAction(self.ICON_SAVE, trans('action.save_as'), menu)
|
|
117
|
+
action.triggered.connect(lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
118
|
+
menu.addAction(action)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
self.window.core.prompt.template.to_menu_options(menu, "input")
|
|
122
|
+
self.window.core.prompt.custom.to_menu_options(menu, "input")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
self.window.core.debug.log(e)
|
|
125
|
+
|
|
126
|
+
action = QAction(self.ICON_SAVE, trans('preset.prompt.save_custom'), menu)
|
|
127
|
+
action.triggered.connect(self.window.controller.presets.save_prompt)
|
|
128
|
+
menu.addAction(action)
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
menu.exec(event.globalPos())
|
|
131
|
+
finally:
|
|
132
|
+
menu.deleteLater()
|
|
120
133
|
|
|
121
134
|
def action_from_clipboard(self):
|
|
122
135
|
"""
|
|
@@ -137,28 +150,26 @@ class ChatInput(QTextEdit):
|
|
|
137
150
|
:param event: key event
|
|
138
151
|
"""
|
|
139
152
|
handled = False
|
|
140
|
-
|
|
153
|
+
key = event.key()
|
|
154
|
+
if key in (Qt.Key_Return, Qt.Key_Enter):
|
|
141
155
|
mode = self.window.core.config.get('send_mode')
|
|
142
|
-
if mode > 0:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if modifiers ==
|
|
156
|
+
if mode > 0:
|
|
157
|
+
modifiers = event.modifiers()
|
|
158
|
+
if mode == 2:
|
|
159
|
+
if modifiers == Qt.ShiftModifier or modifiers == Qt.ControlModifier:
|
|
146
160
|
self.window.controller.chat.input.send_input()
|
|
147
161
|
handled = True
|
|
148
|
-
else:
|
|
149
|
-
modifiers
|
|
150
|
-
if modifiers != QtCore.Qt.ShiftModifier and modifiers != QtCore.Qt.ControlModifier:
|
|
162
|
+
else:
|
|
163
|
+
if modifiers != Qt.ShiftModifier and modifiers != Qt.ControlModifier:
|
|
151
164
|
self.window.controller.chat.input.send_input()
|
|
152
165
|
handled = True
|
|
153
166
|
self.setFocus()
|
|
154
|
-
|
|
155
|
-
# cancel edit
|
|
156
|
-
elif event.key() == Qt.Key_Escape and self.window.controller.ctx.extra.is_editing():
|
|
167
|
+
elif key == Qt.Key_Escape and self.window.controller.ctx.extra.is_editing():
|
|
157
168
|
self.window.controller.ctx.extra.edit_cancel()
|
|
158
169
|
handled = True
|
|
159
170
|
|
|
160
171
|
if not handled:
|
|
161
|
-
super(
|
|
172
|
+
super().keyPressEvent(event)
|
|
162
173
|
|
|
163
174
|
def wheelEvent(self, event):
|
|
164
175
|
"""
|
|
@@ -167,16 +178,70 @@ class ChatInput(QTextEdit):
|
|
|
167
178
|
:param event: Event
|
|
168
179
|
"""
|
|
169
180
|
if event.modifiers() & Qt.ControlModifier:
|
|
170
|
-
|
|
181
|
+
prev = self.value
|
|
182
|
+
dy = event.angleDelta().y()
|
|
183
|
+
if dy > 0:
|
|
171
184
|
if self.value < self.max_font_size:
|
|
172
185
|
self.value += 1
|
|
173
186
|
else:
|
|
174
187
|
if self.value > self.min_font_size:
|
|
175
188
|
self.value -= 1
|
|
176
189
|
|
|
177
|
-
self.
|
|
178
|
-
|
|
179
|
-
|
|
190
|
+
if self.value != prev:
|
|
191
|
+
self.window.core.config.data['font_size.input'] = self.value
|
|
192
|
+
self.window.core.config.save()
|
|
193
|
+
self.window.controller.ui.update_font_size()
|
|
180
194
|
event.accept()
|
|
181
|
-
|
|
182
|
-
|
|
195
|
+
return
|
|
196
|
+
super().wheelEvent(event)
|
|
197
|
+
|
|
198
|
+
# --- Added: attachment button (top-left) ---------------------------------
|
|
199
|
+
|
|
200
|
+
def _init_attachment_button(self):
|
|
201
|
+
"""Create and place the '+' attachment button pinned in the top-left corner."""
|
|
202
|
+
self._attach_margin = 6 # inner padding around the button
|
|
203
|
+
self._attach_offset_y = -6 # shift the button 2px up
|
|
204
|
+
|
|
205
|
+
self._attach_btn = QPushButton(self)
|
|
206
|
+
self._attach_btn.setObjectName("chatInputAttachBtn")
|
|
207
|
+
self._attach_btn.setIconSize(QSize(18, 18)) # icon size (slightly larger for visibility)
|
|
208
|
+
self._attach_btn.setFixedSize(24, 24) # full button size
|
|
209
|
+
self._attach_btn.setCursor(Qt.PointingHandCursor)
|
|
210
|
+
self._attach_btn.setToolTip(trans("attachments.btn.input.add"))
|
|
211
|
+
self._attach_btn.setFocusPolicy(Qt.NoFocus)
|
|
212
|
+
self._attach_btn.setFlat(True) # flat button style
|
|
213
|
+
|
|
214
|
+
self._attach_btn.setIcon(self.ICON_ATTACHMENT)
|
|
215
|
+
self._attach_btn.clicked.connect(self.action_add_attachment)
|
|
216
|
+
self._update_viewport_margins_for_attachment()
|
|
217
|
+
self._reposition_attachment_button()
|
|
218
|
+
|
|
219
|
+
def _update_viewport_margins_for_attachment(self):
|
|
220
|
+
"""Reserve space for the attachment button on the left and apply top text padding."""
|
|
221
|
+
top = self._text_top_padding
|
|
222
|
+
left = self._attach_btn.width() + self._attach_margin * 2 if hasattr(self, "_attach_btn") else self.viewportMargins().left()
|
|
223
|
+
self.setViewportMargins(left, top, 0, 0)
|
|
224
|
+
|
|
225
|
+
def _reposition_attachment_button(self):
|
|
226
|
+
"""Keep the attachment button pinned to the top-left corner."""
|
|
227
|
+
if hasattr(self, "_attach_btn"):
|
|
228
|
+
fw = self.frameWidth()
|
|
229
|
+
x = fw + self._attach_margin
|
|
230
|
+
y = fw + self._attach_margin + self._attach_offset_y # shift up by ~2px
|
|
231
|
+
if y < 0:
|
|
232
|
+
y = 0
|
|
233
|
+
self._attach_btn.move(x, y)
|
|
234
|
+
self._attach_btn.raise_()
|
|
235
|
+
|
|
236
|
+
def resizeEvent(self, event):
|
|
237
|
+
"""Resize event keeps the attachment button in place."""
|
|
238
|
+
super().resizeEvent(event)
|
|
239
|
+
# Keep the attachment button pinned when resizing
|
|
240
|
+
try:
|
|
241
|
+
self._reposition_attachment_button()
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
def action_add_attachment(self):
|
|
246
|
+
"""Add attachment (button click)."""
|
|
247
|
+
self.window.controller.attachment.open_add()
|