pygpt-net 2.6.10__py3-none-any.whl → 2.6.12__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 (46) hide show
  1. pygpt_net/CHANGELOG.txt +10 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +7 -1
  4. pygpt_net/config.py +11 -11
  5. pygpt_net/controller/access/access.py +49 -2
  6. pygpt_net/controller/chat/attachment.py +13 -13
  7. pygpt_net/controller/chat/command.py +4 -4
  8. pygpt_net/controller/chat/common.py +9 -14
  9. pygpt_net/controller/chat/files.py +2 -2
  10. pygpt_net/controller/chat/input.py +4 -4
  11. pygpt_net/controller/chat/output.py +4 -4
  12. pygpt_net/controller/chat/render.py +11 -6
  13. pygpt_net/controller/chat/response.py +7 -7
  14. pygpt_net/controller/chat/stream.py +11 -6
  15. pygpt_net/controller/chat/text.py +15 -10
  16. pygpt_net/controller/command/command.py +7 -7
  17. pygpt_net/controller/ctx/ctx.py +9 -5
  18. pygpt_net/controller/debug/debug.py +2 -2
  19. pygpt_net/core/ctx/bag.py +2 -1
  20. pygpt_net/core/debug/debug.py +17 -3
  21. pygpt_net/core/dispatcher/dispatcher.py +5 -3
  22. pygpt_net/core/events/render.py +3 -0
  23. pygpt_net/core/render/base.py +4 -4
  24. pygpt_net/core/render/web/body.py +83 -88
  25. pygpt_net/core/render/web/parser.py +11 -6
  26. pygpt_net/core/render/web/pid.py +19 -4
  27. pygpt_net/core/render/web/renderer.py +217 -74
  28. pygpt_net/data/config/config.json +3 -3
  29. pygpt_net/data/config/models.json +3 -3
  30. pygpt_net/data/config/presets/agent_openai.json +1 -1
  31. pygpt_net/data/config/presets/agent_openai_assistant.json +1 -1
  32. pygpt_net/data/config/presets/agent_planner.json +1 -1
  33. pygpt_net/data/config/presets/agent_react.json +1 -1
  34. pygpt_net/item/ctx.py +3 -3
  35. pygpt_net/launcher.py +2 -9
  36. pygpt_net/provider/gpt/__init__.py +13 -4
  37. pygpt_net/tools/code_interpreter/body.py +2 -3
  38. pygpt_net/ui/main.py +8 -3
  39. pygpt_net/ui/widget/textarea/html.py +2 -7
  40. pygpt_net/ui/widget/textarea/web.py +52 -28
  41. pygpt_net/utils.py +15 -8
  42. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/METADATA +12 -2
  43. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/RECORD +46 -46
  44. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/LICENSE +0 -0
  45. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/WHEEL +0 -0
  46. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/entry_points.txt +0 -0
pygpt_net/item/ctx.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.08.15 23:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -103,7 +103,7 @@ class CtxItem:
103
103
  if self.input is None:
104
104
  return None
105
105
  if self.hidden_input:
106
- return self.input + "\n\n" + self.hidden_input
106
+ return "\n\n".join(self.input, self.hidden_input)
107
107
  return self.input
108
108
 
109
109
  @property
@@ -116,7 +116,7 @@ class CtxItem:
116
116
  if self.output is None:
117
117
  return None
118
118
  if self.hidden_output:
119
- return self.output + "\n\n" + self.hidden_output
119
+ return "\n\n".join(self.output, self.hidden_output)
120
120
  return self.output
121
121
 
122
122
  def clear_reply(self):
pygpt_net/launcher.py CHANGED
@@ -6,12 +6,9 @@
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.11 00:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
- import asyncio
13
- from qasync import QEventLoop
14
-
15
12
  import os
16
13
  import sys
17
14
  import argparse
@@ -123,8 +120,6 @@ class Launcher:
123
120
  QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
124
121
  self.app = QApplication(sys.argv)
125
122
  self.app.setAttribute(QtCore.Qt.AA_DontUseNativeMenuBar)
126
- self.loop = QEventLoop(self.app)
127
- asyncio.set_event_loop(self.loop)
128
123
  self.window = MainWindow(self.app, args=args)
129
124
  self.shortcut_filter = GlobalShortcutFilter(self.window)
130
125
 
@@ -295,6 +290,4 @@ class Launcher:
295
290
  # self.window.core.debug.mem("INIT") # debug memory usage
296
291
  signal.signal(signal.SIGTERM, self.handle_signal)
297
292
  signal.signal(signal.SIGINT, self.handle_signal)
298
- with self.loop:
299
- self.loop.run_forever()
300
- # sys.exit(self.app.exec())
293
+ sys.exit(self.app.exec())
@@ -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.12 19:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from openai import OpenAI
@@ -63,6 +63,7 @@ class Gpt:
63
63
  self.vision = Vision(window)
64
64
  self.client = None
65
65
  self.locked = False
66
+ self.last_client_args = None # last client args used, for debug purposes
66
67
 
67
68
  def get_client(
68
69
  self,
@@ -78,7 +79,15 @@ class Gpt:
78
79
  """
79
80
  # update client args by mode and model
80
81
  args = self.window.core.models.prepare_client_args(mode, model)
81
- self.client = OpenAI(**args)
82
+ if self.client is None or self.last_client_args != args:
83
+ if self.client is not None:
84
+ try:
85
+ self.client.close() # close previous client if exists
86
+ except Exception as e:
87
+ self.window.core.debug.log(e)
88
+ print("Error closing previous GPT client:", e)
89
+ self.client = OpenAI(**args)
90
+ self.last_client_args = args
82
91
  return self.client
83
92
 
84
93
  def call(self, context: BridgeContext, extra: dict = None) -> bool:
@@ -308,8 +317,8 @@ class Gpt:
308
317
  return
309
318
  if self.client is not None:
310
319
  try:
311
- self.client.close()
312
- self.client = None
320
+ pass
321
+ # self.client.close()
313
322
  except Exception as e:
314
323
  self.window.core.debug.log(e)
315
324
  print("Error closing GPT client:", e)
@@ -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.07.16 15:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -185,8 +185,7 @@ class Body:
185
185
  function highlightCode() {
186
186
  document.querySelectorAll('pre code').forEach(el => {
187
187
  if (!el.classList.contains('hljs')) hljs.highlightElement(el);
188
- });
189
- restoreCollapsedCode();
188
+ });
190
189
  }
191
190
  function scrollToBottom() {
192
191
  getScrollPosition(); // store using bridge
pygpt_net/ui/main.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.08.16 00:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -115,7 +115,10 @@ class MainWindow(QMainWindow, QtStyleTools):
115
115
  if not render_debug:
116
116
  QLoggingCategory.setFilterRules("*.info=false")
117
117
  else:
118
- os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging --log-level=0"
118
+ if "QTWEBENGINE_CHROMIUM_FLAGS" in os.environ:
119
+ os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] += " --enable-logging --log-level=0"
120
+ else:
121
+ os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging --log-level=0"
119
122
 
120
123
  # OpenGL disable
121
124
  if self.core.config.get("render.open_gl") is False:
@@ -247,7 +250,9 @@ class MainWindow(QMainWindow, QtStyleTools):
247
250
 
248
251
  :param message: status message
249
252
  """
250
- self.dispatch(KernelEvent(KernelEvent.STATUS, {"status": str(message)}))
253
+ message = message if isinstance(message, str) else str(message)
254
+ self.dispatch(KernelEvent(KernelEvent.STATUS, {"status": message}))
255
+ del message # free memory
251
256
 
252
257
  @Slot(str)
253
258
  def update_state(self, state: str):
@@ -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.08 21:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -207,12 +207,7 @@ class HtmlOutput(QWebEngineView):
207
207
 
208
208
  :param success: True if loaded successfully
209
209
  """
210
- if success:
211
- event = RenderEvent(RenderEvent.ON_PAGE_LOAD, {
212
- "meta": self.meta,
213
- "tab": self.tab,
214
- })
215
- self.window.dispatch(event)
210
+ pass
216
211
 
217
212
  def get_selected_text(self) -> str:
218
213
  """
@@ -6,9 +6,10 @@
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.16 00:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ from PySide6 import QtCore
12
13
  from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer
13
14
  from PySide6.QtWebChannel import QWebChannel
14
15
  from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage, QWebEngineProfile
@@ -22,6 +23,16 @@ from pygpt_net.core.text.web_finder import WebFinder
22
23
  from pygpt_net.ui.widget.tabs.layout import FocusEventFilter
23
24
  from pygpt_net.utils import trans, mem_clean
24
25
 
26
+ def make_shared_profile():
27
+ prof = QWebEngineProfile("app", None)
28
+ prof.setHttpCacheType(QWebEngineProfile.MemoryHttpCache)
29
+ prof.setHttpCacheMaximumSize(32 * 1024 * 1024) # 32MB
30
+ prof.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
31
+ prof.setSpellCheckEnabled(False)
32
+ return prof
33
+
34
+ SHARED_PROFILE = None
35
+
25
36
  import pygpt_net.icons_rc
26
37
 
27
38
  class ChatWebOutput(QWebEngineView):
@@ -41,17 +52,20 @@ class ChatWebOutput(QWebEngineView):
41
52
  self.filter = FocusEventFilter(self, self.on_focus)
42
53
  self.installEventFilter(self)
43
54
 
44
- self.plain = ""
45
- self.html_content = ""
55
+ global SHARED_PROFILE
56
+ if not SHARED_PROFILE:
57
+ SHARED_PROFILE = make_shared_profile()
58
+
59
+ self.plain = None
60
+ self.html_content = None
46
61
  self.meta = None
47
62
  self.tab = None
48
63
  self.setProperty('class', 'layout-output-web')
49
64
 
50
65
  self._glwidget = None
51
66
  self._glwidget_filter_installed = False
52
- self._profile = self._create_profile(parent=self)
53
67
 
54
- self.setPage(CustomWebEnginePage(self.window, self, profile=self._profile))
68
+ self.setPage(CustomWebEnginePage(self.window, self, profile=SHARED_PROFILE))
55
69
 
56
70
  def _detach_gl_event_filter(self):
57
71
  """Detach OpenGL widget event filter if installed"""
@@ -113,6 +127,7 @@ class ChatWebOutput(QWebEngineView):
113
127
  p.setHttpCacheType(QWebEngineProfile.NoCache)
114
128
  p.setHttpCacheMaximumSize(0)
115
129
  p.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
130
+ p.setSpellCheckEnabled(False)
116
131
  except Exception:
117
132
  pass
118
133
  return p
@@ -167,26 +182,22 @@ class ChatWebOutput(QWebEngineView):
167
182
  return new_view
168
183
 
169
184
  def resetPage(self):
170
- """Reset current page (clear memory)"""
171
- self.meta = None
172
- self.plain = ""
173
- self.html_content = ""
174
-
175
- old_page = self.page()
176
- old_profile = getattr(self, "_profile", None)
177
-
178
- self.setUpdatesEnabled(False)
179
- new_profile = self._create_profile(parent=self)
180
- new_page = CustomWebEnginePage(self.window, self, profile=new_profile)
181
- self.setPage(new_page)
182
-
183
- if old_page:
184
- self._teardown_page(old_page)
185
-
186
- self._release_profile_after_page(old_page, old_profile)
187
- self._profile = new_profile
185
+ """Reset current page without creating a new one"""
186
+ self.plain = None
187
+ self.html_content = None
188
+ p = self.page()
189
+ if not p:
190
+ self.setPage(CustomWebEnginePage(self.window, self, profile=SHARED_PROFILE))
191
+ p = self.page()
188
192
 
189
- QTimer.singleShot(0, lambda: QApplication.sendPostedEvents(None, QEvent.DeferredDelete))
193
+ p.runJavaScript(
194
+ f"""clean();"""
195
+ )
196
+ p.profile().clearHttpCache()
197
+ try:
198
+ p.history().clear()
199
+ except Exception:
200
+ pass
190
201
  mem_clean()
191
202
 
192
203
  def on_delete(self):
@@ -208,8 +219,6 @@ class ChatWebOutput(QWebEngineView):
208
219
  if page:
209
220
  self._teardown_page(page)
210
221
 
211
- self._release_profile_after_page(page, prof)
212
-
213
222
  # safely unhook signals (may not have been hooked)
214
223
  for sig, slot in (
215
224
  (self.loadFinished, self.on_page_loaded),
@@ -311,7 +320,7 @@ class ChatWebOutput(QWebEngineView):
311
320
 
312
321
  # audio read (get text only on click, don't copy immediately)
313
322
  action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
314
- action.triggered.connect(lambda: self.signals.audio_read.emit(self.get_selected_text()))
323
+ action.triggered.connect(self._read_selected_text)
315
324
  menu.addAction(action)
316
325
 
317
326
  # copy to
@@ -320,7 +329,7 @@ class ChatWebOutput(QWebEngineView):
320
329
 
321
330
  # save as (selected) - get selection at the moment of click
322
331
  action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
323
- action.triggered.connect(lambda: self.signals.save_as.emit(self.get_selected_text(), 'txt'))
332
+ action.triggered.connect(self._save_selected_txt)
324
333
  menu.addAction(action)
325
334
  else:
326
335
  # select all
@@ -344,6 +353,20 @@ class ChatWebOutput(QWebEngineView):
344
353
 
345
354
  menu.exec_(self.mapToGlobal(position))
346
355
 
356
+ @Slot()
357
+ def _save_selected_txt(self):
358
+ """Save selected content as text file"""
359
+ self.signals.save_as.emit(self.get_selected_text(), 'txt')
360
+
361
+ @Slot()
362
+ def _read_selected_text(self):
363
+ """
364
+ Read selected text using text-to-speech
365
+ """
366
+ selected_text = self.get_selected_text()
367
+ if selected_text:
368
+ self.signals.audio_read.emit(selected_text)
369
+
347
370
  @Slot()
348
371
  def _save_as_text(self):
349
372
  """
@@ -497,6 +520,7 @@ class CustomWebEnginePage(QWebEnginePage):
497
520
  return super().acceptNavigationRequest(url, _type, isMainFrame)
498
521
 
499
522
  def javaScriptConsoleMessage(self, level, message, line_number, source_id):
523
+ # print("[JS CONSOLE] Line", line_number, ":", message)
500
524
  self.signals.js_message.emit(line_number, message, source_id) # handled in debug controller
501
525
 
502
526
  def cleanup(self):
pygpt_net/utils.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.08.16 00:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -14,7 +14,8 @@ import os
14
14
  import re
15
15
  from datetime import datetime
16
16
 
17
- from PySide6.QtCore import QEvent, QCoreApplication
17
+ from PySide6 import QtCore, QtGui
18
+ from PySide6.QtWidgets import QApplication
18
19
 
19
20
  from pygpt_net.core.locale import Locale
20
21
 
@@ -268,19 +269,25 @@ def mem_clean():
268
269
  gc.collect()
269
270
  except Exception:
270
271
  pass
272
+
271
273
  try:
272
- QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
274
+ QApplication.sendPostedEvents(None, QtCore.QEvent.DeferredDelete)
275
+ QApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
273
276
  except Exception:
274
277
  pass
278
+
279
+ try:
280
+ QtGui.QPixmapCache.clear()
281
+ except Exception:
282
+ pass
283
+
275
284
  try:
276
285
  if sys.platform.startswith("linux"):
277
286
  import ctypes, ctypes.util
278
- libc_path = ctypes.util.find_library("c") or "libc.so.6"
279
- libc = ctypes.CDLL(libc_path, use_errno=True)
287
+ libc = ctypes.CDLL(ctypes.util.find_library("c") or "libc.so.6")
280
288
  if hasattr(libc, "malloc_trim"):
281
- libc.malloc_trim.argtypes = [ctypes.c_size_t]
282
- libc.malloc_trim.restype = ctypes.c_int
283
- ok = bool(libc.malloc_trim(0))
289
+ libc.malloc_trim(0)
290
+ ok = True
284
291
  elif sys.platform == "win32":
285
292
  import ctypes, ctypes.wintypes
286
293
  kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.10
3
+ Version: 2.6.12
4
4
  Summary: Desktop AI Assistant powered by: OpenAI GPT-5, o1, o3, GPT-4, 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: py_gpt,py-gpt,pygpt,desktop,app,o1,o3,gpt-5,gpt,gpt4,gpt-4o,gpt-4v,gpt3.5,gpt-4,gpt-4-vision,gpt-3.5,llama3,mistral,gemini,grok,deepseek,bielik,claude,tts,whisper,vision,chatgpt,dall-e,chat,chatbot,assistant,text completion,image generation,ai,api,openai,api key,langchain,llama-index,ollama,presets,ui,qt,pyside
@@ -108,7 +108,7 @@ Description-Content-Type: text/markdown
108
108
 
109
109
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
110
110
 
111
- Release: **2.6.10** | build: **2025-08-17** | Python: **>=3.10, <3.14**
111
+ Release: **2.6.12** | build: **2025-08-19** | Python: **>=3.10, <3.14**
112
112
 
113
113
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
114
114
  >
@@ -4566,6 +4566,16 @@ may consume additional tokens that are not displayed in the main window.
4566
4566
 
4567
4567
  ## Recent changes:
4568
4568
 
4569
+ **2.6.12 (2025-08-19)**
4570
+
4571
+ - Optimized web renderer memory cleanup.
4572
+
4573
+ **2.6.11 (2025-08-18)**
4574
+
4575
+ - Added the ability to close the dialog window with the Esc key.
4576
+ - Made context item deletion without output refresh.
4577
+ - Optimizations.
4578
+
4569
4579
  **2.6.10 (2025-08-17)**
4570
4580
 
4571
4581
  - Enhanced the handling of the context list.