pygpt-net 2.6.48__py3-none-any.whl → 2.6.50__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 +10 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/ctx/ctx.py +5 -3
- pygpt_net/controller/plugins/plugins.py +1 -1
- pygpt_net/controller/ui/tabs.py +4 -6
- pygpt_net/core/render/plain/pid.py +2 -0
- pygpt_net/core/render/web/renderer.py +4 -10
- pygpt_net/core/tabs/tabs.py +10 -17
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/fixtures/fake_stream.txt +21 -0
- pygpt_net/data/js/app.js +69 -15
- pygpt_net/js_rc.py +12200 -12058
- pygpt_net/tools/code_interpreter/ui/html.py +176 -31
- pygpt_net/tools/code_interpreter/ui/widgets.py +1 -4
- pygpt_net/tools/html_canvas/ui/widgets.py +2 -5
- pygpt_net/ui/widget/tabs/body.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +85 -45
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/METADATA +12 -2
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/RECORD +23 -23
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/entry_points.txt +0 -0
|
@@ -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.
|
|
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
|
-
|
|
116
|
-
|
|
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
|
|
180
|
+
self.tab = None
|
|
181
|
+
self.meta = None
|
|
119
182
|
|
|
120
|
-
# delete page
|
|
121
183
|
page = self.page()
|
|
122
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
637
|
-
|
|
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 =
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
pygpt_net/ui/widget/tabs/body.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
65
|
+
profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
|
|
58
66
|
# self._profile.setHttpCacheType(QWebEngineProfile.NoCache)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
+
except Exception as e:
|
|
149
|
+
self._on_delete_failed(e)
|
|
150
|
+
|
|
112
151
|
try:
|
|
113
152
|
page.history().clear()
|
|
114
|
-
except Exception:
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
+
except Exception as e:
|
|
171
|
+
self._on_delete_failed(e)
|
|
135
172
|
|
|
136
173
|
try:
|
|
137
174
|
page.cleanup()
|
|
138
|
-
except Exception:
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
Version: 2.6.50
|
|
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
|
[](https://snapcraft.io/pygpt)
|
|
120
120
|
|
|
121
|
-
Release: **2.6.
|
|
121
|
+
Release: **2.6.50** | 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,16 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3612
3612
|
|
|
3613
3613
|
## Recent changes:
|
|
3614
3614
|
|
|
3615
|
+
**2.6.50 (2025-09-16)**
|
|
3616
|
+
|
|
3617
|
+
- Optimized: Improved memory cleanup when switching profiles and unloading tabs.
|
|
3618
|
+
- Fix: Resolved missing PID data in text output.
|
|
3619
|
+
- Fix: Enhanced real-time parsing of execute tags.
|
|
3620
|
+
|
|
3621
|
+
**2.6.49 (2025-09-16)**
|
|
3622
|
+
|
|
3623
|
+
- Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
|
|
3624
|
+
|
|
3615
3625
|
**2.6.48 (2025-09-15)**
|
|
3616
3626
|
|
|
3617
3627
|
- Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
|