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.
Files changed (160) hide show
  1. pygpt_net/CHANGELOG.txt +12 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +3 -1
  4. pygpt_net/controller/__init__.py +4 -8
  5. pygpt_net/controller/access/voice.py +2 -2
  6. pygpt_net/controller/agent/llama.py +3 -0
  7. pygpt_net/controller/assistant/batch.py +2 -3
  8. pygpt_net/controller/assistant/editor.py +2 -2
  9. pygpt_net/controller/assistant/files.py +2 -3
  10. pygpt_net/controller/assistant/store.py +2 -2
  11. pygpt_net/controller/audio/audio.py +2 -2
  12. pygpt_net/controller/chat/response.py +4 -0
  13. pygpt_net/controller/ctx/ctx.py +2 -1
  14. pygpt_net/controller/files/files.py +24 -55
  15. pygpt_net/controller/idx/indexer.py +85 -76
  16. pygpt_net/controller/lang/lang.py +52 -34
  17. pygpt_net/controller/model/importer.py +2 -2
  18. pygpt_net/controller/notepad/notepad.py +86 -84
  19. pygpt_net/controller/plugins/settings.py +3 -4
  20. pygpt_net/controller/settings/profile.py +105 -124
  21. pygpt_net/controller/theme/menu.py +154 -57
  22. pygpt_net/controller/theme/nodes.py +51 -44
  23. pygpt_net/controller/theme/theme.py +33 -9
  24. pygpt_net/controller/tools/tools.py +2 -2
  25. pygpt_net/controller/ui/tabs.py +2 -3
  26. pygpt_net/core/agents/observer/evaluation.py +2 -2
  27. pygpt_net/core/agents/runners/loop.py +1 -0
  28. pygpt_net/core/bridge/bridge.py +2 -0
  29. pygpt_net/core/ctx/container.py +13 -12
  30. pygpt_net/core/ctx/output.py +7 -4
  31. pygpt_net/core/debug/console/console.py +2 -2
  32. pygpt_net/core/filesystem/actions.py +1 -2
  33. pygpt_net/core/filesystem/opener.py +261 -0
  34. pygpt_net/core/filesystem/url.py +13 -10
  35. pygpt_net/core/platforms/platforms.py +5 -4
  36. pygpt_net/core/render/plain/helpers.py +2 -5
  37. pygpt_net/core/render/plain/renderer.py +26 -30
  38. pygpt_net/core/render/web/body.py +1 -1
  39. pygpt_net/core/settings/settings.py +43 -13
  40. pygpt_net/core/tabs/tabs.py +20 -13
  41. pygpt_net/data/config/config.json +4 -4
  42. pygpt_net/data/config/models.json +3 -3
  43. pygpt_net/data/css/web-blocks.dark.css +7 -1
  44. pygpt_net/data/css/web-blocks.light.css +5 -2
  45. pygpt_net/data/css/web-chatgpt.dark.css +7 -1
  46. pygpt_net/data/css/web-chatgpt.light.css +3 -0
  47. pygpt_net/data/css/web-chatgpt_wide.dark.css +7 -1
  48. pygpt_net/data/css/web-chatgpt_wide.light.css +3 -0
  49. pygpt_net/data/locale/locale.de.ini +5 -1
  50. pygpt_net/data/locale/locale.en.ini +5 -1
  51. pygpt_net/data/locale/locale.es.ini +5 -1
  52. pygpt_net/data/locale/locale.fr.ini +5 -1
  53. pygpt_net/data/locale/locale.it.ini +5 -1
  54. pygpt_net/data/locale/locale.pl.ini +6 -4
  55. pygpt_net/data/locale/locale.uk.ini +5 -1
  56. pygpt_net/data/locale/locale.zh.ini +5 -1
  57. pygpt_net/plugin/twitter/plugin.py +2 -2
  58. pygpt_net/provider/core/config/patch.py +12 -1
  59. pygpt_net/tools/audio_transcriber/ui/dialogs.py +44 -54
  60. pygpt_net/tools/code_interpreter/body.py +1 -2
  61. pygpt_net/tools/code_interpreter/tool.py +7 -4
  62. pygpt_net/tools/code_interpreter/ui/html.py +1 -3
  63. pygpt_net/tools/code_interpreter/ui/widgets.py +2 -3
  64. pygpt_net/tools/html_canvas/ui/widgets.py +1 -3
  65. pygpt_net/tools/image_viewer/ui/dialogs.py +40 -37
  66. pygpt_net/tools/indexer/ui/widgets.py +2 -4
  67. pygpt_net/tools/media_player/tool.py +2 -5
  68. pygpt_net/tools/media_player/ui/widgets.py +60 -36
  69. pygpt_net/tools/text_editor/ui/widgets.py +18 -19
  70. pygpt_net/tools/translator/ui/widgets.py +39 -35
  71. pygpt_net/ui/base/context_menu.py +9 -4
  72. pygpt_net/ui/dialog/db.py +1 -3
  73. pygpt_net/ui/dialog/models.py +1 -3
  74. pygpt_net/ui/dialog/models_importer.py +2 -4
  75. pygpt_net/ui/dialogs.py +34 -30
  76. pygpt_net/ui/layout/chat/attachments.py +72 -84
  77. pygpt_net/ui/layout/chat/attachments_ctx.py +40 -44
  78. pygpt_net/ui/layout/chat/attachments_uploaded.py +36 -39
  79. pygpt_net/ui/layout/chat/calendar.py +100 -70
  80. pygpt_net/ui/layout/chat/chat.py +23 -17
  81. pygpt_net/ui/layout/chat/input.py +95 -118
  82. pygpt_net/ui/layout/chat/output.py +100 -162
  83. pygpt_net/ui/layout/chat/painter.py +89 -61
  84. pygpt_net/ui/layout/ctx/ctx_list.py +43 -52
  85. pygpt_net/ui/layout/status.py +23 -14
  86. pygpt_net/ui/layout/toolbox/agent.py +27 -38
  87. pygpt_net/ui/layout/toolbox/agent_llama.py +41 -45
  88. pygpt_net/ui/layout/toolbox/assistants.py +42 -38
  89. pygpt_net/ui/layout/toolbox/computer_env.py +32 -23
  90. pygpt_net/ui/layout/toolbox/footer.py +13 -16
  91. pygpt_net/ui/layout/toolbox/image.py +18 -21
  92. pygpt_net/ui/layout/toolbox/indexes.py +46 -89
  93. pygpt_net/ui/layout/toolbox/mode.py +20 -7
  94. pygpt_net/ui/layout/toolbox/model.py +12 -10
  95. pygpt_net/ui/layout/toolbox/presets.py +68 -52
  96. pygpt_net/ui/layout/toolbox/prompt.py +31 -58
  97. pygpt_net/ui/layout/toolbox/toolbox.py +25 -21
  98. pygpt_net/ui/layout/toolbox/vision.py +20 -22
  99. pygpt_net/ui/main.py +2 -4
  100. pygpt_net/ui/menu/about.py +64 -84
  101. pygpt_net/ui/menu/audio.py +87 -63
  102. pygpt_net/ui/menu/config.py +121 -127
  103. pygpt_net/ui/menu/debug.py +69 -76
  104. pygpt_net/ui/menu/file.py +32 -35
  105. pygpt_net/ui/menu/menu.py +2 -3
  106. pygpt_net/ui/menu/plugins.py +69 -33
  107. pygpt_net/ui/menu/theme.py +45 -46
  108. pygpt_net/ui/menu/tools.py +56 -60
  109. pygpt_net/ui/menu/video.py +20 -25
  110. pygpt_net/ui/tray.py +1 -2
  111. pygpt_net/ui/widget/audio/bar.py +1 -3
  112. pygpt_net/ui/widget/audio/input_button.py +3 -4
  113. pygpt_net/ui/widget/calendar/select.py +1 -2
  114. pygpt_net/ui/widget/dialog/base.py +12 -9
  115. pygpt_net/ui/widget/dialog/editor_file.py +20 -23
  116. pygpt_net/ui/widget/dialog/find.py +25 -24
  117. pygpt_net/ui/widget/dialog/profile.py +57 -53
  118. pygpt_net/ui/widget/draw/painter.py +62 -93
  119. pygpt_net/ui/widget/element/button.py +42 -30
  120. pygpt_net/ui/widget/element/checkbox.py +23 -15
  121. pygpt_net/ui/widget/element/group.py +6 -5
  122. pygpt_net/ui/widget/element/labels.py +1 -2
  123. pygpt_net/ui/widget/filesystem/explorer.py +93 -102
  124. pygpt_net/ui/widget/image/display.py +1 -2
  125. pygpt_net/ui/widget/lists/assistant.py +1 -2
  126. pygpt_net/ui/widget/lists/attachment.py +1 -2
  127. pygpt_net/ui/widget/lists/attachment_ctx.py +1 -2
  128. pygpt_net/ui/widget/lists/context.py +2 -4
  129. pygpt_net/ui/widget/lists/index.py +1 -2
  130. pygpt_net/ui/widget/lists/model.py +1 -2
  131. pygpt_net/ui/widget/lists/model_editor.py +1 -2
  132. pygpt_net/ui/widget/lists/model_importer.py +1 -2
  133. pygpt_net/ui/widget/lists/preset.py +1 -2
  134. pygpt_net/ui/widget/lists/preset_plugins.py +1 -2
  135. pygpt_net/ui/widget/lists/profile.py +1 -2
  136. pygpt_net/ui/widget/lists/uploaded.py +1 -2
  137. pygpt_net/ui/widget/option/checkbox.py +2 -4
  138. pygpt_net/ui/widget/option/checkbox_list.py +1 -4
  139. pygpt_net/ui/widget/option/cmd.py +1 -4
  140. pygpt_net/ui/widget/option/dictionary.py +25 -28
  141. pygpt_net/ui/widget/option/input.py +1 -3
  142. pygpt_net/ui/widget/tabs/Input.py +16 -12
  143. pygpt_net/ui/widget/tabs/body.py +5 -3
  144. pygpt_net/ui/widget/tabs/layout.py +41 -28
  145. pygpt_net/ui/widget/tabs/output.py +442 -85
  146. pygpt_net/ui/widget/textarea/calendar_note.py +1 -2
  147. pygpt_net/ui/widget/textarea/editor.py +41 -73
  148. pygpt_net/ui/widget/textarea/find.py +11 -10
  149. pygpt_net/ui/widget/textarea/html.py +3 -6
  150. pygpt_net/ui/widget/textarea/input.py +134 -69
  151. pygpt_net/ui/widget/textarea/notepad.py +54 -38
  152. pygpt_net/ui/widget/textarea/output.py +65 -54
  153. pygpt_net/ui/widget/textarea/search_input.py +5 -4
  154. pygpt_net/ui/widget/textarea/web.py +2 -4
  155. pygpt_net/ui/widget/vision/camera.py +2 -31
  156. {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/METADATA +38 -174
  157. {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/RECORD +160 -159
  158. {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/LICENSE +0 -0
  159. {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/WHEEL +0 -0
  160. {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/entry_points.txt +0 -0
@@ -6,18 +6,17 @@
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.07.22 15:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, QEvent, QTimer
13
13
  from PySide6.QtGui import QAction, QIcon, QKeySequence, QTextCursor, QFontMetrics
14
- from PySide6.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication
14
+ from PySide6.QtWidgets import QTextEdit, QWidget, QVBoxLayout
15
15
 
16
16
  from pygpt_net.core.tabs.tab import Tab
17
17
  from pygpt_net.core.text.finder import Finder
18
18
  from pygpt_net.ui.widget.element.labels import HelpLabel
19
19
  from pygpt_net.utils import trans
20
- import pygpt_net.icons_rc
21
20
 
22
21
 
23
22
  class NotepadWidget(QWidget):
@@ -83,6 +82,10 @@ class NotepadWidget(QWidget):
83
82
  self.deleteLater()
84
83
 
85
84
  class NotepadOutput(QTextEdit):
85
+ ICON_VOLUME = QIcon(":/icons/volume.svg")
86
+ ICON_SAVE = QIcon(":/icons/save.svg")
87
+ ICON_SEARCH = QIcon(":/icons/search.svg")
88
+
86
89
  def __init__(self, window=None):
87
90
  """
88
91
  Notepad output textarea
@@ -104,26 +107,34 @@ class NotepadOutput(QTextEdit):
104
107
  self.installEventFilter(self)
105
108
  self.setProperty('class', 'layout-notepad')
106
109
 
107
- # tabulation
108
110
  metrics = QFontMetrics(self.font())
109
111
  space_width = metrics.horizontalAdvance(" ")
110
112
  self.setTabStopDistance(4 * space_width)
111
113
 
114
+ self._vscroll = self.verticalScrollBar()
115
+ self._vscroll.valueChanged.connect(self._on_scrollbar_value_changed)
116
+ self._restore_attempts = 0
117
+
112
118
  def on_delete(self):
113
119
  """On delete"""
114
120
  if self.finder:
115
121
  self.finder.disconnect() # disconnect finder
116
122
  self.finder = None # delete finder
123
+ try:
124
+ self._vscroll.valueChanged.disconnect(self._on_scrollbar_value_changed)
125
+ except Exception:
126
+ pass
117
127
  self.deleteLater()
118
128
 
119
129
  def showEvent(self, event):
120
130
  super().showEvent(event)
131
+ self._restore_attempts = 0
121
132
  QTimer.singleShot(0, self.restore_scroll_pos)
122
133
 
123
134
  def scroll_to_bottom(self):
124
135
  self.moveCursor(QTextCursor.End)
125
136
  self.ensureCursorVisible()
126
- scroll_bar = self.verticalScrollBar()
137
+ scroll_bar = self._vscroll
127
138
  scroll_bar.setValue(scroll_bar.maximum())
128
139
 
129
140
  def eventFilter(self, source, event):
@@ -133,15 +144,10 @@ class NotepadOutput(QTextEdit):
133
144
  :param source: source
134
145
  :param event: event
135
146
  """
136
- if event.type() == event.Type.FocusIn:
147
+ if event.type() == QEvent.FocusIn:
137
148
  if self.tab is not None:
138
149
  col_idx = self.tab.column_idx
139
150
  self.window.controller.ui.tabs.on_column_focus(col_idx)
140
- elif source == self.verticalScrollBar():
141
- if event.type() == QEvent.Wheel:
142
- self.last_scroll_pos = self.verticalScrollBar().value()
143
- elif event.type() in (QEvent.MouseButtonPress, QEvent.MouseButtonRelease):
144
- self.last_scroll_pos = self.verticalScrollBar().value()
145
151
  return super().eventFilter(source, event)
146
152
 
147
153
  def set_tab(self, tab: Tab):
@@ -152,23 +158,33 @@ class NotepadOutput(QTextEdit):
152
158
  """
153
159
  self.tab = tab
154
160
 
161
+ def setText(self, text: str):
162
+ if self.toPlainText() == text:
163
+ return
164
+ self.setPlainText(text)
165
+
155
166
  def text_changed(self):
156
167
  """On text changed"""
157
168
  if not self.window.core.notepad.locked:
158
169
  self.window.controller.notepad.save(self.id) # use notepad id
159
- self.finder.text_changed()
160
- self.last_scroll_pos = self.verticalScrollBar().value()
170
+ if self.finder is not None:
171
+ self.finder.text_changed()
172
+ self.last_scroll_pos = self._vscroll.value()
173
+
174
+ def _on_scrollbar_value_changed(self, value: int):
175
+ self.last_scroll_pos = value
161
176
 
162
177
  def restore_scroll_pos(self):
163
178
  if self.last_scroll_pos is None:
164
179
  return
165
-
166
- scroll_bar = self.verticalScrollBar()
180
+ scroll_bar = self._vscroll
167
181
  current_max = scroll_bar.maximum()
168
182
  if self.last_scroll_pos > current_max:
169
- self.updateGeometry()
170
- QApplication.processEvents()
171
- QTimer.singleShot(50, self.restore_scroll_pos)
183
+ if self._restore_attempts < 30:
184
+ self._restore_attempts += 1
185
+ QTimer.singleShot(16, self.restore_scroll_pos)
186
+ else:
187
+ scroll_bar.setValue(current_max)
172
188
  else:
173
189
  scroll_bar.setValue(self.last_scroll_pos)
174
190
 
@@ -179,34 +195,30 @@ class NotepadOutput(QTextEdit):
179
195
  :param event: Event
180
196
  """
181
197
  menu = self.createStandardContextMenu()
182
- selected_text = self.textCursor().selectedText()
198
+ cursor = self.textCursor()
199
+ selected_text = cursor.selectedText()
183
200
  if selected_text:
184
- # plain text
185
- plain_text = self.textCursor().selection().toPlainText()
201
+ plain_text = cursor.selection().toPlainText()
186
202
 
187
- # audio read
188
- action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
203
+ action = QAction(self.ICON_VOLUME, trans('text.context_menu.audio.read'), self)
189
204
  action.triggered.connect(self.audio_read_selection)
190
205
  menu.addAction(action)
191
206
 
192
- # copy to (without current notepad)
193
- excluded_id = "notepad_id_{}".format(self.id)
194
- copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, selected_text, excluded=[excluded_id])
207
+ excluded_id = f"notepad_id_{self.id}"
208
+ copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text, excluded=[excluded_id])
195
209
  menu.addMenu(copy_to_menu)
196
210
 
197
- # save as (selected)
198
- action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
211
+ action = QAction(self.ICON_SAVE, trans('action.save_selection_as'), self)
199
212
  action.triggered.connect(
200
213
  lambda: self.window.controller.chat.common.save_text(plain_text))
201
214
  menu.addAction(action)
202
215
  else:
203
- # save as (all)
204
- action = QAction(QIcon(":/icons/save.svg"), trans('action.save_as'), self)
216
+ action = QAction(self.ICON_SAVE, trans('action.save_as'), self)
205
217
  action.triggered.connect(
206
218
  lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
207
219
  menu.addAction(action)
208
220
 
209
- action = QAction(QIcon(":/icons/search.svg"), trans('text.context_menu.find'), self)
221
+ action = QAction(self.ICON_SEARCH, trans('text.context_menu.find'), self)
210
222
  action.triggered.connect(self.find_open)
211
223
  action.setShortcut(QKeySequence("Ctrl+F"))
212
224
  menu.addAction(action)
@@ -246,28 +258,33 @@ class NotepadOutput(QTextEdit):
246
258
  :param event: Event
247
259
  """
248
260
  if event.modifiers() & Qt.ControlModifier:
249
- if event.angleDelta().y() > 0:
261
+ delta = event.angleDelta().y()
262
+ if delta > 0:
250
263
  if self.value < self.max_font_size:
251
264
  self.value += 1
265
+ else:
266
+ return
252
267
  else:
253
268
  if self.value > self.min_font_size:
254
269
  self.value -= 1
270
+ else:
271
+ return
255
272
 
256
273
  self.window.core.config.data['font_size'] = self.value
257
274
  self.window.core.config.save()
258
275
  option = self.window.controller.settings.editor.get_option('font_size')
259
276
  option['value'] = self.value
260
277
  self.window.controller.config.apply(
261
- parent_id='config',
262
- key='font_size',
278
+ parent_id='config',
279
+ key='font_size',
263
280
  option=option,
264
281
  )
265
282
  self.window.controller.ui.update_font_size()
266
283
  event.accept()
267
- self.last_scroll_pos = self.verticalScrollBar().value()
284
+ self.last_scroll_pos = self._vscroll.value()
268
285
  else:
269
286
  super(NotepadOutput, self).wheelEvent(event)
270
- self.last_scroll_pos = self.verticalScrollBar().value()
287
+ self.last_scroll_pos = self._vscroll.value()
271
288
 
272
289
  def focusInEvent(self, e):
273
290
  """
@@ -285,5 +302,4 @@ class NotepadOutput(QTextEdit):
285
302
  :param e: focus event
286
303
  """
287
304
  super(NotepadOutput, self).focusOutEvent(e)
288
- self.window.controller.finder.focus_out(self.finder)
289
-
305
+ self.window.controller.finder.focus_out(self.finder)
@@ -6,30 +6,34 @@
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.05 21:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from PySide6.QtCore import Qt
12
+ from PySide6.QtCore import Qt, QEvent
13
13
  from PySide6.QtWidgets import QTextBrowser
14
14
  from PySide6.QtGui import QAction, QIcon, QTextOption, QKeySequence
15
15
 
16
16
  from pygpt_net.core.text.finder import Finder
17
17
  from pygpt_net.utils import trans
18
- import pygpt_net.icons_rc
19
18
 
20
19
 
21
20
  class ChatOutput(QTextBrowser):
21
+ ICON_VOLUME = QIcon(":/icons/volume.svg")
22
+ ICON_SAVE = QIcon(":/icons/save.svg")
23
+ ICON_SEARCH = QIcon(":/icons/search.svg")
24
+
22
25
  def __init__(self, window=None):
23
26
  """
24
27
  Chat output
25
28
 
26
29
  :param window: main window
27
30
  """
28
- super(ChatOutput, self).__init__(window)
31
+ super().__init__(window)
29
32
  self.window = window
30
33
  self.finder = Finder(window, self)
31
34
  self.setReadOnly(True)
32
35
  self.setAcceptRichText(False)
36
+ self.setUndoRedoEnabled(False)
33
37
  self.setStyleSheet(self.window.controller.theme.style('font.chat.output'))
34
38
  self.value = self.window.core.config.get('font_size')
35
39
  self.max_font_size = 42
@@ -45,15 +49,14 @@ class ChatOutput(QTextBrowser):
45
49
  def on_delete(self):
46
50
  """Clean up on delete"""
47
51
  if self.finder:
48
- self.finder.disconnect() # disconnect finder
49
- self.finder = None # delete finder
52
+ self.finder.disconnect()
53
+ self.finder = None
50
54
 
51
- self.tab = None # clear tab reference
55
+ self.tab = None
52
56
  self.clear()
53
-
54
- # disconnect signals
57
+ self.removeEventFilter(self)
55
58
  self.anchorClicked.disconnect(self.open_external_link)
56
- self.deleteLater() # delete widget
59
+ self.deleteLater()
57
60
 
58
61
  def eventFilter(self, source, event):
59
62
  """
@@ -62,7 +65,7 @@ class ChatOutput(QTextBrowser):
62
65
  :param source: source
63
66
  :param event: event
64
67
  """
65
- if event.type() == event.Type.FocusIn:
68
+ if event.type() == QEvent.FocusIn:
66
69
  if self.tab is not None:
67
70
  col_idx = self.tab.column_idx
68
71
  self.window.controller.ui.tabs.on_column_focus(col_idx)
@@ -91,41 +94,42 @@ class ChatOutput(QTextBrowser):
91
94
  :param event: Event
92
95
  """
93
96
  menu = self.createStandardContextMenu()
94
- selected_text = self.textCursor().selectedText()
97
+ cursor = self.textCursor()
98
+ has_selection = cursor.hasSelection()
95
99
 
96
- if selected_text:
97
- # plain text
98
- plain_text = self.textCursor().selection().toPlainText()
100
+ if has_selection:
101
+ selected_text = cursor.selectedText()
99
102
 
100
- # audio read
101
- action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
103
+ action = QAction(self.ICON_VOLUME, trans('text.context_menu.audio.read'), self)
102
104
  action.triggered.connect(self.audio_read_selection)
103
105
  menu.addAction(action)
104
106
 
105
- # copy to
106
- copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, selected_text)
107
+ copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text)
107
108
  menu.addMenu(copy_to_menu)
108
109
 
109
- # save as (selected)
110
- action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
111
- action.triggered.connect(
112
- lambda: self.window.controller.chat.common.save_text(plain_text)
113
- )
110
+ action = QAction(self.ICON_SAVE, trans('action.save_selection_as'), self)
111
+ action.triggered.connect(self._save_selected_text)
114
112
  menu.addAction(action)
115
113
  else:
116
- # save as (all)
117
- action = QAction(QIcon(":/icons/save.svg"), trans('action.save_as'), self)
118
- action.triggered.connect(
119
- lambda: self.window.controller.chat.common.save_text(self.toPlainText())
120
- )
114
+ action = QAction(self.ICON_SAVE, trans('action.save_as'), self)
115
+ action.triggered.connect(self._save_all_text)
121
116
  menu.addAction(action)
122
117
 
123
- action = QAction(QIcon(":/icons/search.svg"), trans('text.context_menu.find'), self)
118
+ action = QAction(self.ICON_SEARCH, trans('text.context_menu.find'), self)
124
119
  action.triggered.connect(self.find_open)
125
120
  action.setShortcut(QKeySequence("Ctrl+F"))
126
121
  menu.addAction(action)
127
122
 
128
- menu.exec_(event.globalPos())
123
+ menu.exec(event.globalPos())
124
+ menu.deleteLater()
125
+
126
+ def _save_selected_text(self):
127
+ cursor = self.textCursor()
128
+ if cursor.hasSelection():
129
+ self.window.controller.chat.common.save_text(cursor.selection().toPlainText())
130
+
131
+ def _save_all_text(self):
132
+ self.window.controller.chat.common.save_text(self.toPlainText())
129
133
 
130
134
  def audio_read_selection(self):
131
135
  """Read selected text (audio)"""
@@ -138,7 +142,7 @@ class ChatOutput(QTextBrowser):
138
142
  def on_update(self):
139
143
  """On content update"""
140
144
  if self.finder:
141
- self.finder.clear() # clear finder
145
+ self.finder.clear()
142
146
 
143
147
  def keyPressEvent(self, e):
144
148
  """
@@ -147,9 +151,9 @@ class ChatOutput(QTextBrowser):
147
151
  :param e: Event
148
152
  """
149
153
  if e.key() == Qt.Key_F and e.modifiers() & Qt.ControlModifier:
150
- self.find_open() # open find dialog
151
- else:
152
- super(ChatOutput, self).keyPressEvent(e)
154
+ self.find_open()
155
+ return
156
+ super().keyPressEvent(e)
153
157
 
154
158
  def wheelEvent(self, event):
155
159
  """
@@ -158,26 +162,33 @@ class ChatOutput(QTextBrowser):
158
162
  :param event: Event
159
163
  """
160
164
  if event.modifiers() & Qt.ControlModifier:
161
- if event.angleDelta().y() > 0:
162
- if self.value < self.max_font_size:
163
- self.value += 1
165
+ dy = event.angleDelta().y()
166
+ if dy > 0:
167
+ new_value = min(self.value + 1, self.max_font_size)
168
+ elif dy < 0:
169
+ new_value = max(self.value - 1, self.min_font_size)
164
170
  else:
165
- if self.value > self.min_font_size:
166
- self.value -= 1
167
-
168
- self.window.core.config.data['font_size'] = self.value
169
- self.window.core.config.save()
170
- option = self.window.controller.settings.editor.get_option('font_size')
171
- option['value'] = self.value
172
- self.window.controller.config.apply(
173
- parent_id='config',
174
- key='font_size',
175
- option=option,
176
- )
177
- self.window.controller.ui.update_font_size()
171
+ event.accept()
172
+ return
173
+
174
+ if new_value == self.value:
175
+ event.accept()
176
+ return
177
+
178
+ self.value = new_value
179
+
180
+ cfg = self.window.core.config
181
+ cfg.data['font_size'] = new_value
182
+ cfg.save()
183
+
184
+ ctrl = self.window.controller
185
+ option = ctrl.settings.editor.get_option('font_size')
186
+ option['value'] = new_value
187
+ ctrl.config.apply(parent_id='config', key='font_size', option=option)
188
+ ctrl.ui.update_font_size()
178
189
  event.accept()
179
190
  else:
180
- super(ChatOutput, self).wheelEvent(event)
191
+ super().wheelEvent(event)
181
192
 
182
193
  def focusInEvent(self, e):
183
194
  """
@@ -185,5 +196,5 @@ class ChatOutput(QTextBrowser):
185
196
 
186
197
  :param e: focus event
187
198
  """
188
- super(ChatOutput, self).focusInEvent(e)
189
- self.window.controller.finder.focus_in(self.finder)
199
+ super().focusInEvent(e)
200
+ self.window.controller.finder.focus_in(self.finder)
@@ -6,15 +6,14 @@
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.09 15:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtGui import QAction, QIcon
13
- from PySide6.QtWidgets import QLineEdit, QStyle
14
- from PySide6.QtCore import QTimer
13
+ from PySide6.QtWidgets import QLineEdit
14
+ from PySide6.QtCore import QTimer, Slot
15
15
 
16
16
  from pygpt_net.utils import trans
17
- import pygpt_net.icons_rc
18
17
 
19
18
 
20
19
  class CtxSearchInput(QLineEdit):
@@ -56,6 +55,7 @@ class CtxSearchInput(QLineEdit):
56
55
  self._search_timer.stop() # stop the timer to prevent triggering the search action
57
56
  self.window.controller.ctx.search_string_clear()
58
57
 
58
+ @Slot(str)
59
59
  def on_text_changed(self, text):
60
60
  """
61
61
  On text changed
@@ -67,6 +67,7 @@ class CtxSearchInput(QLineEdit):
67
67
  # after a pause in typing
68
68
  self._search_timer.start()
69
69
 
70
+ @Slot()
70
71
  def _execute_search(self):
71
72
  """Invoke the search action after a specified delay."""
72
73
  search_text = 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.19 07:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer
@@ -24,8 +24,6 @@ from pygpt_net.utils import trans, mem_clean
24
24
 
25
25
  SHARED_PROFILE = None
26
26
 
27
- import pygpt_net.icons_rc
28
-
29
27
  class ChatWebOutput(QWebEngineView):
30
28
  def __init__(self, window=None):
31
29
  """
@@ -327,7 +325,7 @@ class ChatWebOutput(QWebEngineView):
327
325
  menu.addAction(action)
328
326
 
329
327
  # copy to
330
- copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, self.get_selected_text())
328
+ copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, self.get_selected_text())
331
329
  menu.addMenu(copy_to_menu)
332
330
 
333
331
  # save as (selected) - get selection at the moment of click
@@ -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: 2023.12.25 21:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -57,33 +57,4 @@ class VideoLabel(QLabel):
57
57
  """
58
58
  if event.button() == Qt.LeftButton:
59
59
  self.window.controller.camera.manual_capture()
60
- elif event.button() == Qt.RightButton:
61
- pass
62
- elif event.button() == Qt.MiddleButton:
63
- pass
64
-
65
- def mouseDoubleClickEvent(self, event):
66
- """
67
- Mouse double click
68
-
69
- :param event: mouse event
70
- """
71
- if event.button() == Qt.LeftButton:
72
- pass
73
- elif event.button() == Qt.RightButton:
74
- pass
75
- elif event.button() == Qt.MiddleButton:
76
- pass
77
-
78
- def mouseReleaseEvent(self, event):
79
- """
80
- Mouse release
81
-
82
- :param event: mouse event
83
- """
84
- if event.button() == Qt.LeftButton:
85
- pass
86
- elif event.button() == Qt.RightButton:
87
- pass
88
- elif event.button() == Qt.MiddleButton:
89
- pass
60
+ super(VideoLabel, self).mousePressEvent(event)