pygpt-net 2.6.48__py3-none-any.whl → 2.6.49__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 CHANGED
@@ -1,3 +1,7 @@
1
+ 2.6.49 (2025-09-16)
2
+
3
+ - Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
4
+
1
5
  2.6.48 (2025-09-15)
2
6
 
3
7
  - Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
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.09.15 00:00:00 #
9
+ # Updated Date: 2025.09.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.48"
17
- __build__ = "2025-09-15"
16
+ __version__ = "2.6.49"
17
+ __build__ = "2025-09-16"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
@@ -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.09.15 22:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List
@@ -479,7 +479,8 @@ class Ctx:
479
479
  id: int,
480
480
  restore_model: bool = True,
481
481
  select_idx: Optional[int] = None,
482
- new_tab: Optional[bool] = False
482
+ new_tab: Optional[bool] = False,
483
+ no_fresh: bool = False
483
484
  ):
484
485
  """
485
486
  Load ctx data
@@ -488,6 +489,7 @@ class Ctx:
488
489
  :param restore_model: restore model if defined in ctx
489
490
  :param select_idx: select index on list after loading
490
491
  :param new_tab: open in new tab
492
+ :param no_fresh: do not fresh output
491
493
  """
492
494
  if new_tab:
493
495
  col_idx = self.window.controller.ui.tabs.column_idx
@@ -500,7 +502,7 @@ class Ctx:
500
502
  if meta is not None:
501
503
  self.set_group(meta.group_id)
502
504
 
503
- if meta is not None:
505
+ if meta is not None and not no_fresh:
504
506
  self.fresh_output(meta)
505
507
 
506
508
  self.reload_config()
@@ -6,14 +6,12 @@
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.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
- import gc
12
- from typing import Any, Optional, Tuple
13
11
 
14
- from PySide6.QtCore import QTimer, QUrl
15
- from PySide6.QtWebEngineCore import QWebEnginePage
12
+ from typing import Any, Optional, Tuple
16
13
 
14
+ from PySide6.QtCore import QTimer
17
15
  from pygpt_net.core.events import AppEvent, RenderEvent
18
16
  from pygpt_net.core.tabs.tab import Tab
19
17
  from pygpt_net.item.ctx import CtxMeta
@@ -363,7 +361,7 @@ class Tabs:
363
361
  if tab.type == Tab.TAB_CHAT and self.column_idx == 1 and not getattr(tab, "loaded", False):
364
362
  meta = self.window.core.ctx.get_meta_by_id(tab.data_id)
365
363
  if meta is not None:
366
- self.window.controller.ctx.load(meta.id)
364
+ self.window.controller.ctx.load(meta.id, no_fresh=True)
367
365
  tab.loaded = True
368
366
 
369
367
  current_ctx = self.window.core.ctx.get_current()
@@ -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.09.14 20:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import gc
@@ -1274,12 +1274,7 @@ class Renderer(BaseRenderer):
1274
1274
  pass
1275
1275
  self._bridge_ready[pid] = False
1276
1276
  self._pending_nodes[pid] = []
1277
- node.hide()
1278
- p = node.page()
1279
- p.triggerAction(QWebEnginePage.Stop)
1280
- p.setUrl(QUrl("about:blank"))
1281
- p.history().clear()
1282
- p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
1277
+ node.unload() # unload web page
1283
1278
  self._stream_reset(pid)
1284
1279
  self.pids[pid].clear(all=True)
1285
1280
  self.pids[pid].loaded = False
@@ -1294,9 +1289,8 @@ class Renderer(BaseRenderer):
1294
1289
  :param meta: context meta
1295
1290
  """
1296
1291
  tab = node.get_tab()
1297
- tab.delete_ref(node)
1298
1292
  layout = tab.child.layout()
1299
- layout.removeWidget(node)
1293
+ tab.child.remove_widget(node)
1300
1294
  self.window.ui.nodes['output'].pop(tab.pid, None)
1301
1295
 
1302
1296
  node.on_delete()
@@ -1307,7 +1301,7 @@ class Renderer(BaseRenderer):
1307
1301
  view.signals.save_as.connect(self.window.controller.chat.render.handle_save_as)
1308
1302
  view.signals.audio_read.connect(self.window.controller.chat.render.handle_audio_read)
1309
1303
 
1310
- layout.addWidget(view)
1304
+ layout.addWidget(view) # tab body layout
1311
1305
  view.setVisible(True)
1312
1306
  self.window.ui.nodes['output'][tab.pid] = view
1313
1307
  try:
@@ -6,16 +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.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import uuid
13
13
  from datetime import datetime
14
14
  from typing import Optional, Any, Dict, Tuple, Union
15
15
 
16
- from PySide6.QtCore import QUrl
17
16
  from PySide6.QtGui import QIcon
18
- from PySide6.QtWebEngineCore import QWebEnginePage
19
17
  from PySide6.QtWidgets import QVBoxLayout, QWidget, QLayout
20
18
 
21
19
  from pygpt_net.ui.widget.tabs.body import TabBody
@@ -301,30 +299,24 @@ class Tabs:
301
299
  if tab.type == Tab.TAB_CHAT:
302
300
  node = self.window.ui.nodes['output'].get(tab.pid)
303
301
  if node:
304
- node.hide()
305
- p = node.page()
306
- p.triggerAction(QWebEnginePage.Stop)
307
- p.setUrl(QUrl("about:blank"))
308
- p.history().clear()
309
- p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
310
- tab.delete_ref(node)
311
- layout = tab.child.layout()
312
- layout.removeWidget(node)
302
+ node.unload() # unload web page
303
+ tab.child.remove_widget(node)
313
304
  self.window.ui.nodes['output'].pop(pid, None)
314
305
  node.on_delete()
315
306
  node_plain = self.window.ui.nodes['output_plain'].get(tab.pid)
316
307
  if node_plain:
317
- tab.delete_ref(node_plain)
318
- layout = tab.child.layout()
319
- layout.removeWidget(node_plain)
308
+ tab.child.remove_widget(node_plain)
320
309
  self.window.ui.nodes['output_plain'].pop(pid, None)
321
310
  node_plain.on_delete()
322
311
 
323
312
  if tab.type in (Tab.TAB_CHAT, Tab.TAB_NOTEPAD, Tab.TAB_TOOL):
324
313
  tab.cleanup() # unload assigned data from memory
325
314
 
315
+ # tab.delete_refs()
316
+
326
317
  except Exception as e:
327
318
  print(f"Error unloading tab {pid}: {e}")
319
+ self.window.core.debug.log(e)
328
320
 
329
321
  column_idx = tab.column_idx
330
322
  self.window.ui.layout.get_tabs_by_idx(column_idx).removeTab(tab.idx)
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.48",
4
- "app.version": "2.6.48",
5
- "updated_at": "2025-09-15T00:00:00"
3
+ "version": "2.6.49",
4
+ "app.version": "2.6.49",
5
+ "updated_at": "2025-09-16T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.48",
4
- "app.version": "2.6.48",
5
- "updated_at": "2025-09-15T08:03:34"
3
+ "version": "2.6.49",
4
+ "app.version": "2.6.49",
5
+ "updated_at": "2025-09-16T08:03:34"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -6,16 +6,16 @@
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.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
13
13
  import re
14
14
  from typing import Optional
15
15
 
16
- from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer
16
+ from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer, QUrl, QCoreApplication, QEventLoop
17
17
  from PySide6.QtWebChannel import QWebChannel
18
- from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage
18
+ from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage, QWebEngineProfile
19
19
  from PySide6.QtWebEngineWidgets import QWebEngineView
20
20
  from PySide6.QtGui import QAction, QIcon, QKeySequence
21
21
  from PySide6.QtWidgets import QMenu
@@ -25,6 +25,7 @@ from pygpt_net.core.text.web_finder import WebFinder
25
25
  from pygpt_net.tools.code_interpreter.body import Body
26
26
  from pygpt_net.utils import trans
27
27
 
28
+
28
29
  class CodeBlock:
29
30
  def __init__(
30
31
  self,
@@ -109,32 +110,119 @@ class HtmlOutput(QWebEngineView):
109
110
  self.is_dialog = False
110
111
  self.nodes = [] # code blocks
111
112
 
113
+ # OpenGL widgets
114
+ self._glwidget = None
115
+ self._glwidget_filter_installed = False
116
+ self._unloaded = False # flag to check if unloaded
117
+ self._destroyed = False
118
+
119
+ # self._profile = self._make_profile(self)
120
+ self.setPage(CustomWebEnginePage(self.window, self, profile=None))
121
+
122
+ def _make_profile(self, parent=None) -> QWebEngineProfile:
123
+ """Make profile"""
124
+ profile = QWebEngineProfile()
125
+ profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
126
+ profile.setSpellCheckEnabled(False)
127
+ return profile
128
+
129
+ def _detach_gl_event_filter(self):
130
+ """Detach OpenGL widget event filter if installed"""
131
+ if self._glwidget and self._glwidget_filter_installed:
132
+ try:
133
+ self._glwidget.removeEventFilter(self)
134
+ except Exception as e:
135
+ self._on_delete_failed(e)
136
+ self._glwidget = None
137
+ self._glwidget_filter_installed = False
138
+
139
+ def _on_delete_failed(self, e):
140
+ """
141
+ Handle delete failure
142
+
143
+ :param e: Exception instance
144
+ """
145
+ pass
146
+ # self.window.core.debug.log(e)
147
+
148
+ def unload(self):
149
+ """Unload the current page and free resources"""
150
+ try:
151
+ self.hide()
152
+ p = self.page()
153
+ p.triggerAction(QWebEnginePage.Stop)
154
+ p.setUrl(QUrl("about:blank"))
155
+ p.history().clear()
156
+ p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
157
+ except Exception as e:
158
+ self._on_delete_failed(e)
159
+ finally:
160
+ self._unloaded = True
161
+
112
162
  def on_delete(self):
113
163
  """Clean up on delete"""
164
+ if self._destroyed:
165
+ return
166
+ if not self._unloaded:
167
+ self.unload()
168
+
169
+ self.hide()
170
+ self._detach_gl_event_filter()
171
+
114
172
  if self.finder:
115
- self.finder.disconnect() # disconnect finder
116
- self.finder = None # delete finder
173
+ try:
174
+ self.finder.disconnect()
175
+ except Exception as e:
176
+ self._on_delete_failed(e)
177
+ finally:
178
+ self.finder = None
117
179
 
118
- self.tab = None # clear tab reference
180
+ self.tab = None
181
+ self.meta = None
119
182
 
120
- # delete page
121
183
  page = self.page()
122
- if page:
123
- if hasattr(page, 'bridge'):
124
- page.bridge.deleteLater()
125
- if hasattr(page, 'channel'):
126
- page.channel.deleteLater()
127
- if hasattr(page, 'signals') and page.signals:
128
- page.signals.deleteLater()
129
- page.deleteLater() # delete page
184
+ prof = page.profile()
130
185
 
131
186
  # disconnect signals
132
- self.loadFinished.disconnect(self.on_page_loaded)
133
- self.customContextMenuRequested.disconnect(self.on_context_menu)
134
- self.signals.save_as.disconnect(self.window.controller.chat.render.handle_save_as)
135
- self.signals.audio_read.disconnect(self.window.controller.chat.render.handle_audio_read)
187
+ try:
188
+ self.loadFinished.disconnect()
189
+ self.customContextMenuRequested.disconnect()
190
+ self.signals.save_as.disconnect()
191
+ self.signals.audio_read.disconnect()
192
+ except Exception as e:
193
+ self._on_delete_failed(e)
194
+
195
+ try:
196
+ if hasattr(page, "setWebChannel"):
197
+ page.setWebChannel(None)
198
+ except Exception as e:
199
+ self._on_delete_failed(e)
200
+
201
+ try:
202
+ page.cleanup()
203
+ except Exception as e:
204
+ self._on_delete_failed(e)
205
+
206
+ """
207
+ if prof is not None:
208
+ try:
209
+ prof.deleteLater()
210
+ except Exception as e:
211
+ self._on_delete_failed(e)
212
+ """
213
+
214
+ try:
215
+ self.deleteLater()
216
+ except Exception as e:
217
+ self._on_delete_failed(e)
218
+
219
+ try:
220
+ QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
221
+ QCoreApplication.processEvents(QEventLoop.AllEvents, 50)
222
+ except Exception as e:
223
+ self._on_delete_failed(e)
136
224
 
137
- self.deleteLater() # delete widget
225
+ self._destroyed = True
138
226
 
139
227
  def init(self, force: bool = False):
140
228
  """
@@ -164,7 +252,7 @@ class HtmlOutput(QWebEngineView):
164
252
 
165
253
  def reload(self):
166
254
  """Reload HTML output"""
167
- #return
255
+ # return
168
256
  self.reload_css()
169
257
 
170
258
  def get_nodes(self) -> list:
@@ -203,7 +291,7 @@ class HtmlOutput(QWebEngineView):
203
291
 
204
292
  :param text: text
205
293
  """
206
- self.plain+= str(text)
294
+ self.plain += str(text)
207
295
 
208
296
  def set_plaintext(self, text: str):
209
297
  """
@@ -267,7 +355,7 @@ class HtmlOutput(QWebEngineView):
267
355
  if node.content != "":
268
356
  self.nodes.append(node)
269
357
  if self.loaded:
270
- self.insert_output(node)
358
+ self.insert_output(node)
271
359
  self.update_current_content()
272
360
  QTimer.singleShot(100, self.scroll_to_bottom) # wait for rendering to complete
273
361
 
@@ -508,7 +596,7 @@ class HtmlOutput(QWebEngineView):
508
596
  menu.addSeparator()
509
597
  action = QAction(QIcon(":/icons/search.svg"), trans('text.context_menu.find'), self)
510
598
  action.triggered.connect(self.find_open)
511
- #action.setShortcut(QKeySequence("Ctrl+F"))
599
+ # action.setShortcut(QKeySequence("Ctrl+F"))
512
600
  menu.addAction(action)
513
601
 
514
602
  menu.exec_(self.mapToGlobal(position))
@@ -615,14 +703,20 @@ class HtmlOutput(QWebEngineView):
615
703
  if (event.type() == QEvent.ChildAdded and
616
704
  source is self and
617
705
  event.child().isWidgetType()):
706
+ self._detach_gl_event_filter()
618
707
  self._glwidget = event.child()
619
- self._glwidget.installEventFilter(self)
708
+ try:
709
+ self._glwidget.installEventFilter(self)
710
+ self._glwidget_filter_installed = True
711
+ except Exception:
712
+ self._glwidget = None
713
+ self._glwidget_filter_installed = False
620
714
  elif event.type() == event.Type.MouseButtonPress:
621
715
  if self.tab:
622
716
  col_idx = self.tab.column_idx
623
717
  self.window.controller.ui.tabs.on_column_focus(col_idx)
624
718
  elif event.type() == event.Type.FocusIn:
625
- if self.tab is not None:
719
+ if self.tab:
626
720
  col_idx = self.tab.column_idx
627
721
  self.window.controller.ui.tabs.on_column_focus(col_idx)
628
722
  elif event.type() == QEvent.KeyPress:
@@ -633,10 +727,17 @@ class HtmlOutput(QWebEngineView):
633
727
 
634
728
  class CustomWebEnginePage(QWebEnginePage):
635
729
  """Custom WebEnginePage to handle web events"""
636
- def __init__(self, window, parent):
637
- super(CustomWebEnginePage, self).__init__(window)
730
+
731
+ def __init__(self, window, view, profile: QWebEngineProfile = None):
732
+
733
+ # use the profile if provided, otherwise the default
734
+ if profile is not None:
735
+ super(CustomWebEnginePage, self).__init__(profile, view)
736
+ else:
737
+ super(CustomWebEnginePage, self).__init__(view)
738
+
638
739
  self.window = window
639
- self.parent = parent
740
+ self.parent = view
640
741
  self.signals = WebEnginePageSignals()
641
742
  self.findTextFinished.connect(self.on_find_finished)
642
743
  self.zoomFactorChanged.connect(self.on_view_changed)
@@ -687,7 +788,7 @@ class CustomWebEnginePage(QWebEnginePage):
687
788
  """On selection changed"""
688
789
  pass
689
790
 
690
- def acceptNavigationRequest(self, url, _type, isMainFrame):
791
+ def acceptNavigationRequest(self, url, _type, isMainFrame):
691
792
  """
692
793
  On navigation (link click) event
693
794
 
@@ -698,7 +799,7 @@ class CustomWebEnginePage(QWebEnginePage):
698
799
  if _type == QWebEnginePage.NavigationTypeLinkClicked:
699
800
  self.window.core.filesystem.url.handle(url)
700
801
  return False
701
- return super().acceptNavigationRequest(url, _type, isMainFrame)
802
+ return super().acceptNavigationRequest(url, _type, isMainFrame)
702
803
 
703
804
  def javaScriptConsoleMessage(self, level, message, line_number, source_id):
704
805
  """
@@ -711,9 +812,40 @@ class CustomWebEnginePage(QWebEnginePage):
711
812
  """
712
813
  self.signals.js_message.emit(line_number, message, source_id) # handled in debug controller
713
814
 
815
+ def cleanup(self):
816
+ """Cleanup method to release resources"""
817
+ try:
818
+ self.findTextFinished.disconnect()
819
+ self.zoomFactorChanged.disconnect()
820
+ self.selectionChanged.disconnect()
821
+ except Exception:
822
+ pass
823
+
824
+ if self.bridge:
825
+ try:
826
+ self.bridge.cleanup()
827
+ except Exception:
828
+ pass
829
+ self.bridge = None
830
+
831
+ if self.channel:
832
+ try:
833
+ self.channel.unregisterObject("bridge")
834
+ except Exception:
835
+ pass
836
+ self.channel = None
837
+
838
+ if self.signals:
839
+ try:
840
+ self.signals.deleteLater()
841
+ except Exception:
842
+ pass
843
+ self.signals = None
844
+
714
845
 
715
846
  class Bridge(QObject):
716
847
  """Bridge between Python and JavaScript"""
848
+
717
849
  def __init__(self, window):
718
850
  super(Bridge, self).__init__(window)
719
851
  self.window = window
@@ -754,10 +886,23 @@ class Bridge(QObject):
754
886
  """
755
887
  self.window.controller.chat.render.scroll = pos
756
888
 
889
+ def cleanup(self):
890
+ """Cleanup method to release resources"""
891
+ if self.window:
892
+ try:
893
+ self.window = None
894
+ except Exception:
895
+ pass
896
+
897
+ # delete the bridge object
898
+ self.deleteLater()
899
+
900
+
757
901
  class WebEngineSignals(QObject):
758
902
  save_as = Signal(str, str)
759
903
  audio_read = Signal(str)
760
904
 
905
+
761
906
  class WebEnginePageSignals(QObject):
762
907
  js_message = Signal(int, str, str) # on Javascript message
763
908
 
@@ -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.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6 import QtCore
@@ -100,9 +100,6 @@ class ToolWidget:
100
100
  :return: QVBoxLayout
101
101
  """
102
102
  self.output = HtmlOutput(self.window, self.tool)
103
- self.output.setPage(
104
- CustomWebEnginePage(self.window, self.output)
105
- )
106
103
  self.output.signals.save_as.connect(self.window.controller.chat.render.handle_save_as)
107
104
  self.output.signals.audio_read.connect(self.window.controller.chat.render.handle_audio_read)
108
105
 
@@ -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.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, Slot, QUrl, QObject, Signal
@@ -14,7 +14,7 @@ from PySide6.QtWidgets import QVBoxLayout, QCheckBox, QHBoxLayout
14
14
 
15
15
  from pygpt_net.ui.widget.element.labels import HelpLabel
16
16
  from pygpt_net.ui.widget.textarea.editor import BaseCodeEditor
17
- from pygpt_net.ui.widget.textarea.html import HtmlOutput, CustomWebEnginePage
17
+ from pygpt_net.ui.widget.textarea.html import HtmlOutput
18
18
  from pygpt_net.utils import trans
19
19
 
20
20
 
@@ -61,9 +61,6 @@ class ToolWidget:
61
61
  :return: QVBoxLayout
62
62
  """
63
63
  self.output = CanvasOutput(self.window)
64
- self.output.setPage(
65
- CustomWebEnginePage(self.window, self.output)
66
- )
67
64
  self.edit = CanvasEdit(self.window)
68
65
  self.edit.setVisible(False)
69
66
  self.edit.textChanged.connect(
@@ -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.09.14 20:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any
@@ -35,6 +35,28 @@ class TabBody(QTabWidget):
35
35
  self.on_delete(self)
36
36
  self.delete_refs()
37
37
 
38
+ def remove_widget(self, widget: QWidget) -> None:
39
+ """
40
+ Remove widget from tab body
41
+
42
+ :param widget: widget to remove
43
+ """
44
+ layout = self.layout()
45
+ if layout is not None:
46
+ layout.removeWidget(widget)
47
+ self.delete_ref(widget)
48
+
49
+ def remove_all_widgets(self) -> None:
50
+ """
51
+ Remove all widgets from tab body
52
+ """
53
+ layout = self.layout()
54
+ if layout is not None:
55
+ while layout.count():
56
+ item = layout.takeAt(0)
57
+ widget = item.widget()
58
+ layout.removeWidget(widget)
59
+
38
60
  def add_ref(self, ref: Any) -> None:
39
61
  """
40
62
  Add reference to widget in this tab
@@ -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.09.11 08:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QUrl, QCoreApplication, QEventLoop
@@ -22,6 +22,7 @@ from pygpt_net.core.text.web_finder import WebFinder
22
22
  from pygpt_net.ui.widget.tabs.layout import FocusEventFilter
23
23
  from pygpt_net.utils import trans
24
24
 
25
+
25
26
  class ChatWebOutput(QWebEngineView):
26
27
  def __init__(self, window=None):
27
28
  """
@@ -47,113 +48,153 @@ class ChatWebOutput(QWebEngineView):
47
48
  # OpenGL widgets
48
49
  self._glwidget = None
49
50
  self._glwidget_filter_installed = False
51
+ self._unloaded = False
52
+ self._destroyed = False
50
53
 
51
54
  # set the page with a shared profile
52
55
  self.setUpdatesEnabled(False) # disable updates until the page is set, re-enable in `on_page_loaded`
53
56
 
54
- self._profile = QWebEngineProfile(self)
57
+ # self._profile = self._make_profile(self)
58
+ self.setPage(CustomWebEnginePage(self.window, self, profile=None))
59
+
60
+ def _make_profile(self, parent=None) -> QWebEngineProfile:
61
+ """Make profile"""
62
+ profile = QWebEngineProfile(parent)
55
63
  # self._profile.setHttpCacheType(QWebEngineProfile.MemoryHttpCache)
56
64
  # self._profile.setHttpCacheMaximumSize(32 * 1024 * 1024) # 32MB
57
- self._profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
65
+ profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
58
66
  # self._profile.setHttpCacheType(QWebEngineProfile.NoCache)
59
- self._profile.setSpellCheckEnabled(False)
60
- # self._profile.setOffTheRecord(True)
61
- self.setPage(CustomWebEnginePage(self.window, self, profile=self._profile))
67
+ profile.setSpellCheckEnabled(False)
68
+ return profile
69
+
62
70
 
63
71
  def _detach_gl_event_filter(self):
64
72
  """Detach OpenGL widget event filter if installed"""
65
73
  if self._glwidget and self._glwidget_filter_installed:
66
74
  try:
67
75
  self._glwidget.removeEventFilter(self)
68
- except Exception:
69
- pass
76
+ except Exception as e:
77
+ self._on_delete_failed(e)
70
78
  self._glwidget = None
71
79
  self._glwidget_filter_installed = False
72
80
 
81
+ def _on_delete_failed(self, e):
82
+ """
83
+ Handle delete failure
84
+
85
+ :param e: Exception instance
86
+ """
87
+ pass
88
+ # self.window.core.debug.log(e)
89
+
90
+ def unload(self):
91
+ """Unload the current page and free resources"""
92
+ try:
93
+ self.hide()
94
+ p = self.page()
95
+ p.triggerAction(QWebEnginePage.Stop)
96
+ p.setUrl(QUrl("about:blank"))
97
+ p.history().clear()
98
+ p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
99
+ except Exception as e:
100
+ self._on_delete_failed(e)
101
+ finally:
102
+ self._unloaded = True
103
+
73
104
  def on_delete(self):
74
105
  """Clean up on delete"""
106
+ if self._destroyed:
107
+ return
108
+ if not self._unloaded:
109
+ self.unload()
110
+
75
111
  self.hide()
76
112
  self._detach_gl_event_filter()
77
-
113
+
78
114
  if self.finder:
79
115
  try:
80
116
  self.finder.disconnect()
81
- except Exception:
82
- pass
83
- self.finder = None
117
+ except Exception as e:
118
+ self._on_delete_failed(e)
119
+ finally:
120
+ self.finder = None
84
121
 
85
122
  self.tab = None
86
123
  self.meta = None
87
124
 
88
125
  # safely unhook signals (may not have been hooked)
89
126
  for sig, slot in (
90
- (self.loadFinished, self.on_page_loaded),
91
- (self.customContextMenuRequested, self.on_context_menu),
92
- (self.signals.save_as, getattr(self.window.controller.chat.render, "handle_save_as", None)),
93
- (self.signals.audio_read, getattr(self.window.controller.chat.render, "handle_audio_read", None)),
127
+ (self.loadFinished, self.on_page_loaded),
128
+ (self.customContextMenuRequested, self.on_context_menu),
129
+ (self.signals.save_as, getattr(self.window.controller.chat.render, "handle_save_as", None)),
130
+ (self.signals.audio_read, getattr(self.window.controller.chat.render, "handle_audio_read", None)),
94
131
  ):
95
132
  if slot:
96
133
  try:
97
134
  sig.disconnect(slot)
98
- except Exception:
99
- pass
135
+ except Exception as e:
136
+ self._on_delete_failed(e)
100
137
 
101
138
  page = self.page()
102
139
  page.set_loaded(False)
103
140
 
104
141
  try:
105
142
  page.triggerAction(QWebEnginePage.Stop)
106
- except Exception:
107
- pass
143
+ except Exception as e:
144
+ self._on_delete_failed(e)
145
+
108
146
  try:
109
147
  page.setUrl(QUrl("about:blank"))
110
- except Exception:
111
- pass
148
+ except Exception as e:
149
+ self._on_delete_failed(e)
150
+
112
151
  try:
113
152
  page.history().clear()
114
- except Exception:
115
- pass
153
+ except Exception as e:
154
+ self._on_delete_failed(e)
155
+
116
156
  try:
117
157
  page.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
118
- except Exception:
119
- pass
158
+ except Exception as e:
159
+ self._on_delete_failed(e)
160
+
120
161
  try:
121
162
  if hasattr(page, "setWebChannel"):
122
163
  page.setWebChannel(None)
123
- except Exception:
124
- pass
125
- try:
126
- self.disconnect()
127
- except Exception:
128
- pass
164
+ except Exception as e:
165
+ self._on_delete_failed(e)
129
166
 
130
167
  prof = None
131
168
  try:
132
169
  prof = page.profile()
133
- except Exception:
134
- pass
170
+ except Exception as e:
171
+ self._on_delete_failed(e)
135
172
 
136
173
  try:
137
174
  page.cleanup()
138
- except Exception:
139
- pass
175
+ except Exception as e:
176
+ self._on_delete_failed(e)
140
177
 
178
+ """
141
179
  if prof is not None:
142
180
  try:
143
181
  prof.deleteLater()
144
- except Exception:
145
- pass
182
+ except Exception as e:
183
+ self._on_delete_failed(e)
184
+ """
146
185
 
147
186
  try:
148
187
  self.deleteLater()
149
- except Exception:
150
- pass
188
+ except Exception as e:
189
+ self._on_delete_failed(e)
151
190
 
152
191
  try:
153
192
  QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
154
193
  QCoreApplication.processEvents(QEventLoop.AllEvents, 50)
155
- except Exception:
156
- pass
194
+ except Exception as e:
195
+ self._on_delete_failed(e)
196
+
197
+ self._destroyed = True
157
198
 
158
199
  def eventFilter(self, source, event):
159
200
  """
@@ -390,6 +431,7 @@ class ChatWebOutput(QWebEngineView):
390
431
 
391
432
  class CustomWebEnginePage(QWebEnginePage):
392
433
  """Custom WebEnginePage to handle web events"""
434
+
393
435
  def __init__(self, window, view, profile: QWebEngineProfile = None):
394
436
 
395
437
  # use the profile if provided, otherwise the default
@@ -495,12 +537,10 @@ class CustomWebEnginePage(QWebEnginePage):
495
537
  pass
496
538
  self.signals = None
497
539
 
498
- # delete the page object
499
- self.deleteLater()
500
-
501
540
 
502
541
  class Bridge(QObject):
503
542
  """Bridge between Python and JavaScript"""
543
+
504
544
  def __init__(self, window, parent=None):
505
545
  super(Bridge, self).__init__(parent)
506
546
  self.window = window
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.48
3
+ Version: 2.6.49
4
4
  Summary: Desktop AI Assistant powered by: OpenAI GPT-5, GPT-4, o1, o3, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, internet access, file handling, command execution and more.
5
5
  License: MIT
6
6
  Keywords: ai,api,api key,app,assistant,bielik,chat,chatbot,chatgpt,claude,dall-e,deepseek,desktop,gemini,gpt,gpt-3.5,gpt-4,gpt-4-vision,gpt-4o,gpt-5,gpt-oss,gpt3.5,gpt4,grok,langchain,llama-index,llama3,mistral,o1,o3,ollama,openai,presets,py-gpt,py_gpt,pygpt,pyside,qt,text completion,tts,ui,vision,whisper
@@ -118,7 +118,7 @@ Description-Content-Type: text/markdown
118
118
 
119
119
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
120
120
 
121
- Release: **2.6.48** | build: **2025-09-15** | Python: **>=3.10, <3.14**
121
+ Release: **2.6.49** | build: **2025-09-16** | Python: **>=3.10, <3.14**
122
122
 
123
123
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
124
124
  >
@@ -3612,6 +3612,10 @@ may consume additional tokens that are not displayed in the main window.
3612
3612
 
3613
3613
  ## Recent changes:
3614
3614
 
3615
+ **2.6.49 (2025-09-16)**
3616
+
3617
+ - Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
3618
+
3615
3619
  **2.6.48 (2025-09-15)**
3616
3620
 
3617
3621
  - Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
@@ -1,6 +1,6 @@
1
- pygpt_net/CHANGELOG.txt,sha256=nkKLzPZGyGTI2MiFPCaFRWDXarnV1idNnrd2_5ZSfOY,106248
1
+ pygpt_net/CHANGELOG.txt,sha256=p65ExEC2HTbdlw2RFL75_4I2T5TAc2R0j4Pi4uNh2vc,106378
2
2
  pygpt_net/LICENSE,sha256=dz9sfFgYahvu2NZbx4C1xCsVn9GVer2wXcMkFRBvqzY,1146
3
- pygpt_net/__init__.py,sha256=in6Dc_96ApNHm7bEI0zUiJCtcqyQj2Rxrwvq1LQLm1Q,1373
3
+ pygpt_net/__init__.py,sha256=vxaKvvX5rLWJ_cLCuVaUaVEvyJon-6Ja6Ga0CG4wb4k,1373
4
4
  pygpt_net/app.py,sha256=prS80WfKSu8U_Ox9oUdxgzgHgRB1nvQQAMFTNltiECY,21954
5
5
  pygpt_net/app_core.py,sha256=PwBOV9wZLtr-O6SxBiazABhYXMHH8kZ6OgbvSv2OiZA,3827
6
6
  pygpt_net/config.py,sha256=SCps_FfwdrynVAgpn37Ci1qTN8BFC05IGl9sYIi9e0w,16720
@@ -72,7 +72,7 @@ pygpt_net/controller/config/field/textarea.py,sha256=Ln545IHzXBeFIjnfMIpmlUr-V3w
72
72
  pygpt_net/controller/config/placeholder.py,sha256=-PWPNILPVkxMsY64aYnKTWvgUIvx7KA2Nwfd2LW_K30,16711
73
73
  pygpt_net/controller/ctx/__init__.py,sha256=0wH7ziC75WscBW8cxpeGBwEz5tolo_kCxGPoz2udI_E,507
74
74
  pygpt_net/controller/ctx/common.py,sha256=1oR7cUgVzO_asqiRln4L4vZaxXsssAPtGyAp-B0FUE4,6409
75
- pygpt_net/controller/ctx/ctx.py,sha256=m2Obm6l7dW7umdS89xz3r2V57oJQnLuqaz1ls7BqUvY,46152
75
+ pygpt_net/controller/ctx/ctx.py,sha256=bjz0qCFRAoFpAR3EglV-Lo3yX-sRIo9I85i1sgzfaCs,46250
76
76
  pygpt_net/controller/ctx/extra.py,sha256=0r-G6Tlm9WPDkLRmgPDlgyRr_XLfCJntnUGlYPJiXVw,8598
77
77
  pygpt_net/controller/ctx/summarizer.py,sha256=UNsq-JTARblGNT97uSMpZEVzdUuDJ8YA2j2dw9R2X3o,3079
78
78
  pygpt_net/controller/debug/__init__.py,sha256=dOJGTICjvTtrPIEDOsxCzcOHsfu8AFPLpSKbdN0q0KI,509
@@ -146,7 +146,7 @@ pygpt_net/controller/tools/__init__.py,sha256=ds63rOuwLEIe-SlY_sQkhWSdXS0lfVwseU
146
146
  pygpt_net/controller/tools/tools.py,sha256=bWxdwL3J2-WHBS3MBiKsS3kTW_rQI_nS9z8-8iKifKg,2920
147
147
  pygpt_net/controller/ui/__init__.py,sha256=cxfh2SYeEDATGAZpcYDqCxYfp4KReQ1CYehevSf89EU,507
148
148
  pygpt_net/controller/ui/mode.py,sha256=35J5TRCg33D-RP9epFsWD-ZO-jQ2O0Q41iuqrePC1zM,9107
149
- pygpt_net/controller/ui/tabs.py,sha256=Zm08Azux-m00Yg4F2aPyyx3Zk3KNXxHd5yBpXSa7ylg,29242
149
+ pygpt_net/controller/ui/tabs.py,sha256=Pih53K5G6hD3dWeCF_EWaG5VIF6-x1z1Vs1enmpA6q0,29190
150
150
  pygpt_net/controller/ui/ui.py,sha256=w6rxJ0bNk_43Hd0vZB0roxZo7h-AmISEhr8ZiqcBTgA,8496
151
151
  pygpt_net/controller/ui/vision.py,sha256=tnzllFV2-sYDHfrP12ATY6ZKi6FcGtQofrsoKF6UcCU,2407
152
152
  pygpt_net/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -354,13 +354,13 @@ pygpt_net/core/render/web/debug.py,sha256=784RYXF6inn_bkRtYD1_FllQSyk67JmxKGWPiA
354
354
  pygpt_net/core/render/web/helpers.py,sha256=8-JkbEOOLoCEAkylJWr8yk3aIliQBMyqcatsyW99VWY,7340
355
355
  pygpt_net/core/render/web/parser.py,sha256=pDFc9Tf8P-jvrDilXyT1fukcQHbixHRJ9Dn9hF10Gko,12892
356
356
  pygpt_net/core/render/web/pid.py,sha256=F33x_OtrHL9BDMXx_JUbfo8-DOiN4vo1Tv4rrRVADTo,4343
357
- pygpt_net/core/render/web/renderer.py,sha256=fsdFr6jnO69KnKQVXHDkhshotI_wH0T4tpWQ0GNI4kg,69068
357
+ pygpt_net/core/render/web/renderer.py,sha256=baP3vaZHk21k_M6aIiMIaj7p-Bi3CtgmkR3L7EQ_MOE,68859
358
358
  pygpt_net/core/render/web/syntax_highlight.py,sha256=QSLGF5cJL_Xeqej7_TYwY_5C2w9enXV_cMEuaJ3C43U,2005
359
359
  pygpt_net/core/settings/__init__.py,sha256=GQ6_gJ2jf_Chm7ZuZLvkcvEh_sfMDVMBieeoJi2iPI4,512
360
360
  pygpt_net/core/settings/settings.py,sha256=Ix06y-gJ3q7NJDf55XAWBBYulBLpinBqzYqsytH_9mo,8686
361
361
  pygpt_net/core/tabs/__init__.py,sha256=reDufOWWDQsZwfvtnXrFQROEdl9nqoKI7S3bFA3D9As,508
362
362
  pygpt_net/core/tabs/tab.py,sha256=oUQEBWLmVEAp8ck7ur6BI9Ax98csQEHgKiAf1BnWjQk,6138
363
- pygpt_net/core/tabs/tabs.py,sha256=1ZXr29hf5CHT5k6vtZfoGQk1U274enDgw_l-7WeT1Zw,32786
363
+ pygpt_net/core/tabs/tabs.py,sha256=D18xA9dkQw2AhkA6Mbqmcc3-x9GHLA3mo1k2pSciwng,32359
364
364
  pygpt_net/core/text/__init__.py,sha256=6aEjrckL5kWVfyxpi5mVpSPB6XWV83e_30g_V5meL1M,19
365
365
  pygpt_net/core/text/finder.py,sha256=NBzYUE_Av3oZH8RlCrSe6EeLcHpfz79WJV_vSK0P1jI,6656
366
366
  pygpt_net/core/text/text.py,sha256=WyQdXx4TpBGgr3XU6AhPILvhaipB57S2XtIs8FYif84,3217
@@ -395,8 +395,8 @@ pygpt_net/css_rc.py,sha256=PX6g9z5BsD-DXISuR2oq3jHcjiKfcJ4HsgcHez6wGMc,27762
395
395
  pygpt_net/data/audio/click_off.mp3,sha256=aNiRDP1pt-Jy7ija4YKCNFBwvGWbzU460F4pZWZDS90,65201
396
396
  pygpt_net/data/audio/click_on.mp3,sha256=qfdsSnthAEHVXzeyN4LlC0OvXuyW8p7stb7VXtlvZ1k,65201
397
397
  pygpt_net/data/audio/ok.mp3,sha256=LTiV32pEBkpUGBkKkcOdOFB7Eyt_QoP2Nv6c5AaXftk,32256
398
- pygpt_net/data/config/config.json,sha256=9Mf-6mkX3gbGUS7ozcnaq38U7Xn4YaojLpPbJ1yjqyE,30845
399
- pygpt_net/data/config/models.json,sha256=10vP2grytO1djdniFLxS2K3uUvvjPdkCdNmrPugMW1M,118192
398
+ pygpt_net/data/config/config.json,sha256=H-h9zQr_rSrCy5MsXResKny8XtUDaUE3S98q4477h0Y,30845
399
+ pygpt_net/data/config/models.json,sha256=Jrqq-Jgyx730ZlXrdDASa5MN57BQYRkmEieBKrl_x_k,118192
400
400
  pygpt_net/data/config/modes.json,sha256=IpjLOm428_vs6Ma9U-YQTNKJNtZw-qyM1lwhh73xl1w,2111
401
401
  pygpt_net/data/config/presets/agent_code_act.json,sha256=GYHqhxtKFLUCvRI3IJAJ7Qe1k8yD9wGGNwManldWzlI,754
402
402
  pygpt_net/data/config/presets/agent_openai.json,sha256=bpDJgLRey_effQkzFRoOEGd4aHUrmzeODSDdNzrf62I,730
@@ -2340,13 +2340,13 @@ pygpt_net/tools/code_interpreter/body.py,sha256=jDEHJuAI8U1RjV0Dhm5CcL4Y8b09UNDw
2340
2340
  pygpt_net/tools/code_interpreter/tool.py,sha256=XH3_ZUWp1tp21rGlj6-ULI723DXfY1xs7dN9Ye03Pm8,24185
2341
2341
  pygpt_net/tools/code_interpreter/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2342
2342
  pygpt_net/tools/code_interpreter/ui/dialogs.py,sha256=iYCtmn_gPMKhvy8Ygga_oYPVg80zP3-10SZgv0O6xYQ,5429
2343
- pygpt_net/tools/code_interpreter/ui/html.py,sha256=Ql0tXEIsjlfXbRznoRY8F_6XTK-RpkTLMfaAkIv_9rc,23551
2344
- pygpt_net/tools/code_interpreter/ui/widgets.py,sha256=77bUzSme7fJIALivC0EXLWhZhx8BwiLkKywrUQ1J3LM,18117
2343
+ pygpt_net/tools/code_interpreter/ui/html.py,sha256=8XI_RVZsOm6x95LTp3Lol4xmuTb_Mnb08lnNHxp6QpM,27414
2344
+ pygpt_net/tools/code_interpreter/ui/widgets.py,sha256=noOvecQNHcZKDfyfmgnupNr8KtY8WPvfbds63z6vOV0,18020
2345
2345
  pygpt_net/tools/html_canvas/__init__.py,sha256=7FScUoJWFBsF6Rbmt14zb2YEERcywW_xAeX0GTAHhX4,508
2346
2346
  pygpt_net/tools/html_canvas/tool.py,sha256=F0rozMO1rva7ABr1hXhOBCW9XWq9hL8RjTWsI6cp-wc,9875
2347
2347
  pygpt_net/tools/html_canvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2348
2348
  pygpt_net/tools/html_canvas/ui/dialogs.py,sha256=zw4XusCoZ-52ks8ogcasEVAxDHQwSHh88FtW2rk4HP0,4825
2349
- pygpt_net/tools/html_canvas/ui/widgets.py,sha256=8SmIQ0M3XUy-4u-MIAqerBcMN5VAaq6QhU7RjewkYaQ,5168
2349
+ pygpt_net/tools/html_canvas/ui/widgets.py,sha256=Yp_IP1VhAF5zRymdQ2ADG_wObKOf44rNgI58Vn2c3_4,5050
2350
2350
  pygpt_net/tools/image_viewer/__init__.py,sha256=BQLr8Kg670xPAyv_qoRA_EjWq5WZclqoc4OpTk5V11k,508
2351
2351
  pygpt_net/tools/image_viewer/tool.py,sha256=8cIcEG9R0HGUXwikD8XOxQHqt0QZkgB4LzpiQ4GAWwU,9269
2352
2352
  pygpt_net/tools/image_viewer/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -2549,7 +2549,7 @@ pygpt_net/ui/widget/option/toggle.py,sha256=ainYgH-1UKeHf_BP0yZ48luD0k4zAvZ5jMLd
2549
2549
  pygpt_net/ui/widget/option/toggle_label.py,sha256=JZwI_mpqLX6Ml9ovijXMWBHTT0t_nf3aYTqRqTYFtpY,2271
2550
2550
  pygpt_net/ui/widget/tabs/Input.py,sha256=ELHpaWjhHJdKRhtTjDIByaMF_BqaHCyKwEWDofm0Gls,1875
2551
2551
  pygpt_net/ui/widget/tabs/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
2552
- pygpt_net/ui/widget/tabs/body.py,sha256=BAj1uw6n0vShg01tWFWMfq66yM93zW_lpEzCOuLtOsw,3756
2552
+ pygpt_net/ui/widget/tabs/body.py,sha256=dNHfsQ1c6qVylg44I3PvDN21zyJKOkUz2KdH93St5l8,4373
2553
2553
  pygpt_net/ui/widget/tabs/layout.py,sha256=6b6bN04IFS0I0cYvq-nUxN2eWwHVUBiNwPXoNgLokM4,6628
2554
2554
  pygpt_net/ui/widget/tabs/output.py,sha256=OwlRYcAwb12kMp5RT-DjsEOkXHG9FNQfJuVQBL7XnKA,25082
2555
2555
  pygpt_net/ui/widget/textarea/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
@@ -2566,12 +2566,12 @@ pygpt_net/ui/widget/textarea/output.py,sha256=krWta3GHwdlPOqcxLln150bo7iUOtbFL_y
2566
2566
  pygpt_net/ui/widget/textarea/rename.py,sha256=NwuGRIeWMo7WfsMguAFpTqdOz1eTiXbxrDXGsbWF_TY,1358
2567
2567
  pygpt_net/ui/widget/textarea/search_input.py,sha256=aoOlunBwxn-z3gIMNKfnghHX00sC36wQHl87dRlDJlM,5227
2568
2568
  pygpt_net/ui/widget/textarea/url.py,sha256=xbNQxoM5fYI1ZWbvybQkPmNPrIq3yhtNPBOSOWftZCg,1337
2569
- pygpt_net/ui/widget/textarea/web.py,sha256=t6ZppPxmkpw6z2hgL0w0-0EF_ez_0ZPi-JYWiTnpN6E,18086
2569
+ pygpt_net/ui/widget/textarea/web.py,sha256=FGS0NGSpzKgtP8AzMwnC-LHDXEOY7baHImmnxsbIyGY,19279
2570
2570
  pygpt_net/ui/widget/vision/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
2571
2571
  pygpt_net/ui/widget/vision/camera.py,sha256=v1qEncaZr5pXocO5Cpk_lsgfCMvfFigdJmzsYfzvCl0,1877
2572
2572
  pygpt_net/utils.py,sha256=7lZj_YSzx7ZfvqFtjYThEvRJNSBZzrJyK7ZxDAtYPAQ,9708
2573
- pygpt_net-2.6.48.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
2574
- pygpt_net-2.6.48.dist-info/METADATA,sha256=SJJStYIxg52hrjfdNCw-suGv3WT4cBv2se5k5Q5gpKE,163206
2575
- pygpt_net-2.6.48.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
2576
- pygpt_net-2.6.48.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
2577
- pygpt_net-2.6.48.dist-info/RECORD,,
2573
+ pygpt_net-2.6.49.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
2574
+ pygpt_net-2.6.49.dist-info/METADATA,sha256=AykZ3KQvjl_5RGn7sLhu76V2NsPRRCFZDPg9aKAB7Uo,163340
2575
+ pygpt_net-2.6.49.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
2576
+ pygpt_net-2.6.49.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
2577
+ pygpt_net-2.6.49.dist-info/RECORD,,