pygpt-net 2.6.47__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.
Files changed (35) hide show
  1. pygpt_net/CHANGELOG.txt +8 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app_core.py +39 -39
  4. pygpt_net/controller/__init__.py +72 -62
  5. pygpt_net/controller/ctx/common.py +0 -7
  6. pygpt_net/controller/ctx/ctx.py +176 -8
  7. pygpt_net/controller/ctx/extra.py +3 -3
  8. pygpt_net/controller/settings/editor.py +3 -1
  9. pygpt_net/controller/theme/common.py +8 -2
  10. pygpt_net/controller/ui/tabs.py +10 -43
  11. pygpt_net/core/ctx/ctx.py +79 -26
  12. pygpt_net/core/render/web/renderer.py +4 -10
  13. pygpt_net/core/tabs/tabs.py +50 -11
  14. pygpt_net/data/config/config.json +3 -3
  15. pygpt_net/data/config/models.json +3 -3
  16. pygpt_net/data/css/web-blocks.css +256 -270
  17. pygpt_net/data/css/web-chatgpt.css +276 -301
  18. pygpt_net/data/css/web-chatgpt_wide.css +286 -294
  19. pygpt_net/provider/core/config/patch.py +9 -0
  20. pygpt_net/provider/core/ctx/db_sqlite/storage.py +19 -5
  21. pygpt_net/tools/code_interpreter/ui/html.py +176 -31
  22. pygpt_net/tools/code_interpreter/ui/widgets.py +1 -4
  23. pygpt_net/tools/html_canvas/ui/widgets.py +2 -5
  24. pygpt_net/ui/__init__.py +9 -14
  25. pygpt_net/ui/layout/chat/chat.py +2 -2
  26. pygpt_net/ui/layout/ctx/ctx_list.py +71 -1
  27. pygpt_net/ui/widget/lists/base.py +32 -1
  28. pygpt_net/ui/widget/lists/context.py +45 -2
  29. pygpt_net/ui/widget/tabs/body.py +23 -1
  30. pygpt_net/ui/widget/textarea/web.py +85 -45
  31. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/METADATA +10 -2
  32. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/RECORD +35 -35
  33. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/LICENSE +0 -0
  34. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/WHEEL +0 -0
  35. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/entry_points.txt +0 -0
@@ -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
@@ -134,46 +132,15 @@ class Tabs:
134
132
 
135
133
  def reload(self):
136
134
  """Reload tabs"""
137
- self.unload_current()
135
+ columns = self.window.ui.layout.columns
136
+ for col in columns:
137
+ col.setUpdatesEnabled(False)
138
+ self.window.core.tabs.remove_all()
138
139
  self.window.core.tabs.reload()
139
140
  self.window.dispatch(RenderEvent(RenderEvent.PREPARE))
140
141
  self.debug()
141
-
142
- def unload_current(self):
143
- """Unload current tabs from memory"""
144
- tabs = self.window.core.tabs.pids
145
- for pid in tabs:
146
- tab = self.window.core.tabs.get_tab_by_pid(pid)
147
- if tab is not None:
148
- try:
149
- if tab.type == Tab.TAB_CHAT:
150
- node = self.window.ui.nodes['output'].get(tab.pid)
151
- if node:
152
- node.hide()
153
- p = node.page()
154
- p.triggerAction(QWebEnginePage.Stop)
155
- p.setUrl(QUrl("about:blank"))
156
- p.history().clear()
157
- p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
158
- tab.delete_ref(node)
159
- layout = tab.child.layout()
160
- layout.removeWidget(node)
161
- self.window.ui.nodes['output'].pop(pid, None)
162
- node.on_delete()
163
- node_plain = self.window.ui.nodes['output_plain'].get(tab.pid)
164
- if node_plain:
165
- tab.delete_ref(node_plain)
166
- layout = tab.child.layout()
167
- layout.removeWidget(node_plain)
168
- self.window.ui.nodes['output_plain'].pop(pid, None)
169
- node_plain.on_delete()
170
- tab.cleanup()
171
- except Exception as e:
172
- print(f"Error unloading tab {pid}: {e}")
173
- try:
174
- gc.collect()
175
- except Exception:
176
- pass
142
+ for col in columns:
143
+ col.setUpdatesEnabled(True)
177
144
 
178
145
  def reload_after(self):
179
146
  """Reload tabs after"""
@@ -394,7 +361,7 @@ class Tabs:
394
361
  if tab.type == Tab.TAB_CHAT and self.column_idx == 1 and not getattr(tab, "loaded", False):
395
362
  meta = self.window.core.ctx.get_meta_by_id(tab.data_id)
396
363
  if meta is not None:
397
- self.window.controller.ctx.load(meta.id)
364
+ self.window.controller.ctx.load(meta.id, no_fresh=True)
398
365
  tab.loaded = True
399
366
 
400
367
  current_ctx = self.window.core.ctx.get_current()
pygpt_net/core/ctx/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.24 02:00:00 #
9
+ # Updated Date: 2025.09.15 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -1003,7 +1003,7 @@ class Ctx:
1003
1003
  for item in reversed(history_items):
1004
1004
  num = from_ctx(item, mode, model)
1005
1005
  new_total = tokens + num
1006
- if max_tokens > 0 and new_total > max_tokens:
1006
+ if 0 < max_tokens < new_total:
1007
1007
  break
1008
1008
  tokens = new_total
1009
1009
  context_tokens += num
@@ -1240,49 +1240,102 @@ class Ctx:
1240
1240
  return len(self.filters_labels) < num_all
1241
1241
 
1242
1242
  def load_meta(self):
1243
- """Load ctx list from provider"""
1244
- limit = 0
1243
+ """Load ctx list: pinned and grouped unlimited; ungrouped not pinned paginated directly in SQL."""
1244
+ # base package size (per page)
1245
+ base_limit = 0
1245
1246
  if self.window.core.config.has('ctx.records.limit'):
1246
- limit = int(self.window.core.config.get('ctx.records.limit') or 0)
1247
-
1248
- if "is_important" not in self.filters and "indexed_ts" not in self.filters:
1249
- filters_pinned = self.get_parsed_filters()
1250
- filters_pinned['is_important'] = {
1251
- "mode": "=",
1252
- "value": 1,
1253
- }
1254
- meta_pinned = self.provider.get_meta(
1247
+ try:
1248
+ base_limit = int(self.window.core.config.get('ctx.records.limit') or 0)
1249
+ except Exception:
1250
+ base_limit = 0
1251
+
1252
+ # total loaded (persisted); fallback to base when missing
1253
+ try:
1254
+ loaded_total = int(self.window.core.config.get('ctx.records.limit.total') or 0)
1255
+ except Exception:
1256
+ loaded_total = 0
1257
+ if loaded_total <= 0:
1258
+ loaded_total = base_limit
1259
+
1260
+ # Common filters (labels etc.)
1261
+ common_filters = self.get_parsed_filters()
1262
+
1263
+ # If explicit filters target a narrow subset (legacy path), keep old behavior
1264
+ if "is_important" in self.filters or "indexed_ts" in self.filters:
1265
+ limit = 0 if base_limit == 0 else loaded_total
1266
+ self.meta = self.provider.get_meta(
1255
1267
  search_string=self.search_string,
1256
1268
  order_by='updated_ts',
1257
1269
  order_direction='DESC',
1258
- limit=0,
1259
- filters=filters_pinned,
1270
+ limit=limit,
1271
+ offset=0,
1272
+ filters=common_filters,
1260
1273
  search_content=self.is_search_content(),
1261
1274
  )
1275
+ return
1262
1276
 
1263
- filters = self.get_parsed_filters()
1264
- meta_unpinned = self.provider.get_meta(
1277
+ # 1) Pinned (important) – unlimited
1278
+ filters_pinned = self.get_parsed_filters()
1279
+ filters_pinned['is_important'] = {"mode": "=", "value": 1}
1280
+ meta_pinned = self.provider.get_meta(
1281
+ search_string=self.search_string,
1282
+ order_by='updated_ts',
1283
+ order_direction='DESC',
1284
+ limit=0,
1285
+ offset=0,
1286
+ filters=filters_pinned,
1287
+ search_content=self.is_search_content(),
1288
+ )
1289
+
1290
+ # 2) Grouped – unlimited
1291
+ filters_grouped = self.get_parsed_filters()
1292
+ filters_grouped['group_id'] = {"mode": ">", "value": 0}
1293
+ meta_grouped = self.provider.get_meta(
1294
+ search_string=self.search_string,
1295
+ order_by='updated_ts',
1296
+ order_direction='DESC',
1297
+ limit=0,
1298
+ offset=0,
1299
+ filters=filters_grouped,
1300
+ search_content=self.is_search_content(),
1301
+ )
1302
+
1303
+ # 3) Ungrouped & not pinned – paginate directly in SQL
1304
+ # If base_limit == 0 -> unlimited (no paging)
1305
+ filters_ungrp = self.get_parsed_filters()
1306
+ filters_ungrp['is_important'] = {"mode": "=", "value": 0}
1307
+ filters_ungrp['group_id'] = {"mode": "NULL_OR_ZERO", "value": 0} # special mode handled in Storage
1308
+
1309
+ if base_limit <= 0:
1310
+ meta_ungrouped = self.provider.get_meta(
1265
1311
  search_string=self.search_string,
1266
1312
  order_by='updated_ts',
1267
1313
  order_direction='DESC',
1268
- limit=limit,
1269
- filters=filters,
1314
+ limit=0, # unlimited
1315
+ offset=0,
1316
+ filters=filters_ungrp,
1270
1317
  search_content=self.is_search_content(),
1271
1318
  )
1272
-
1273
- self.meta = {**meta_pinned, **meta_unpinned}
1274
-
1275
1319
  else:
1276
- filters = self.get_parsed_filters()
1277
- self.meta = self.provider.get_meta(
1320
+ # Always take the top-N ungrouped newest items directly from DB
1321
+ take = max(0, int(loaded_total or 0))
1322
+ meta_ungrouped = self.provider.get_meta(
1278
1323
  search_string=self.search_string,
1279
1324
  order_by='updated_ts',
1280
1325
  order_direction='DESC',
1281
- limit=limit,
1282
- filters=filters,
1326
+ limit=take,
1327
+ offset=0,
1328
+ filters=filters_ungrp,
1283
1329
  search_content=self.is_search_content(),
1284
1330
  )
1285
1331
 
1332
+ # Compose final dict with deterministic order: pinned -> grouped -> ungrouped
1333
+ combined = {}
1334
+ combined.update(meta_pinned)
1335
+ combined.update(meta_grouped)
1336
+ combined.update(meta_ungrouped)
1337
+ self.meta = combined
1338
+
1286
1339
  def load_tmp_meta(self, meta_id: int):
1287
1340
  """
1288
1341
  Load tmp meta
@@ -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,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
  import uuid
@@ -234,20 +234,38 @@ class Tabs:
234
234
  tab.icon = self.icons[tab.type]
235
235
 
236
236
  if tab.type == Tab.TAB_CHAT: # chat
237
- self.add_chat(tab)
238
- self.window.core.ctx.output.mapping[tab.pid] = tab.data_id # restore pid => meta.id mapping
239
- self.window.core.ctx.output.last_pids[tab.data_id] = tab.pid
240
- self.window.core.ctx.output.last_pid = tab.pid
237
+ try:
238
+ self.add_chat(tab)
239
+ self.window.core.ctx.output.mapping[tab.pid] = tab.data_id # restore pid => meta.id mapping
240
+ self.window.core.ctx.output.last_pids[tab.data_id] = tab.pid
241
+ self.window.core.ctx.output.last_pid = tab.pid
242
+ except Exception as e:
243
+ print("Error restoring chat tab:", e)
241
244
  elif tab.type == Tab.TAB_NOTEPAD: # notepad
242
- self.add_notepad(tab)
245
+ try:
246
+ self.add_notepad(tab)
247
+ except Exception as e:
248
+ print("Error restoring notepad tab:", e)
243
249
  elif tab.type == Tab.TAB_FILES: # files
244
- self.add_tool_explorer(tab)
250
+ try:
251
+ self.add_tool_explorer(tab)
252
+ except Exception as e:
253
+ print("Error restoring explorer tab:", e)
245
254
  elif tab.type == Tab.TAB_TOOL_PAINTER: # painter
246
- self.add_tool_painter(tab)
255
+ try:
256
+ self.add_tool_painter(tab)
257
+ except Exception as e:
258
+ print("Error restoring painter tab:", e)
247
259
  elif tab.type == Tab.TAB_TOOL_CALENDAR: # calendar
248
- self.add_tool_calendar(tab)
260
+ try:
261
+ self.add_tool_calendar(tab)
262
+ except Exception as e:
263
+ print("Error restoring calendar tab:", e)
249
264
  elif tab.type == Tab.TAB_TOOL: # custom tools, id 100+
250
- self.add_tool(tab)
265
+ try:
266
+ self.add_tool(tab)
267
+ except Exception as e:
268
+ print("Error restoring tool tab:", e)
251
269
 
252
270
  self.pids[tab.pid] = tab
253
271
  self.last_pid = self.get_max_pid()
@@ -277,8 +295,29 @@ class Tabs:
277
295
  tab = self.get_tab_by_pid(pid)
278
296
  if tab is None:
279
297
  return
298
+ try:
299
+ if tab.type == Tab.TAB_CHAT:
300
+ node = self.window.ui.nodes['output'].get(tab.pid)
301
+ if node:
302
+ node.unload() # unload web page
303
+ tab.child.remove_widget(node)
304
+ self.window.ui.nodes['output'].pop(pid, None)
305
+ node.on_delete()
306
+ node_plain = self.window.ui.nodes['output_plain'].get(tab.pid)
307
+ if node_plain:
308
+ tab.child.remove_widget(node_plain)
309
+ self.window.ui.nodes['output_plain'].pop(pid, None)
310
+ node_plain.on_delete()
311
+
312
+ if tab.type in (Tab.TAB_CHAT, Tab.TAB_NOTEPAD, Tab.TAB_TOOL):
313
+ tab.cleanup() # unload assigned data from memory
314
+
315
+ # tab.delete_refs()
316
+
317
+ except Exception as e:
318
+ print(f"Error unloading tab {pid}: {e}")
319
+ self.window.core.debug.log(e)
280
320
 
281
- tab.cleanup() # unload assigned data from memory
282
321
  column_idx = tab.column_idx
283
322
  self.window.ui.layout.get_tabs_by_idx(column_idx).removeTab(tab.idx)
284
323
  del self.pids[pid]
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.47",
4
- "app.version": "2.6.47",
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.47",
4
- "app.version": "2.6.47",
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": {