pygpt-net 2.6.10__py3-none-any.whl → 2.6.11__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 (37) hide show
  1. pygpt_net/CHANGELOG.txt +6 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/config.py +11 -11
  4. pygpt_net/controller/access/access.py +49 -2
  5. pygpt_net/controller/chat/attachment.py +13 -13
  6. pygpt_net/controller/chat/command.py +4 -4
  7. pygpt_net/controller/chat/common.py +9 -14
  8. pygpt_net/controller/chat/files.py +2 -2
  9. pygpt_net/controller/chat/input.py +4 -4
  10. pygpt_net/controller/chat/output.py +4 -4
  11. pygpt_net/controller/chat/render.py +11 -6
  12. pygpt_net/controller/chat/response.py +5 -5
  13. pygpt_net/controller/chat/stream.py +2 -3
  14. pygpt_net/controller/chat/text.py +15 -10
  15. pygpt_net/controller/command/command.py +7 -7
  16. pygpt_net/controller/ctx/ctx.py +9 -5
  17. pygpt_net/controller/debug/debug.py +2 -2
  18. pygpt_net/core/debug/debug.py +6 -1
  19. pygpt_net/core/dispatcher/dispatcher.py +5 -3
  20. pygpt_net/core/events/render.py +3 -0
  21. pygpt_net/core/render/base.py +4 -4
  22. pygpt_net/core/render/web/body.py +35 -82
  23. pygpt_net/core/render/web/parser.py +11 -6
  24. pygpt_net/core/render/web/renderer.py +71 -37
  25. pygpt_net/data/config/config.json +3 -3
  26. pygpt_net/data/config/models.json +3 -3
  27. pygpt_net/data/config/presets/agent_openai.json +1 -1
  28. pygpt_net/data/config/presets/agent_openai_assistant.json +1 -1
  29. pygpt_net/data/config/presets/agent_planner.json +1 -1
  30. pygpt_net/data/config/presets/agent_react.json +1 -1
  31. pygpt_net/ui/main.py +4 -2
  32. pygpt_net/ui/widget/textarea/web.py +17 -3
  33. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.11.dist-info}/METADATA +8 -2
  34. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.11.dist-info}/RECORD +37 -37
  35. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.11.dist-info}/LICENSE +0 -0
  36. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.11.dist-info}/WHEEL +0 -0
  37. {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.11.dist-info}/entry_points.txt +0 -0
@@ -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.18 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -31,6 +31,7 @@ from pygpt_net.core.events import RenderEvent
31
31
 
32
32
 
33
33
  class Renderer(BaseRenderer):
34
+
34
35
  NODE_INPUT = 0
35
36
  NODE_OUTPUT = 1
36
37
  ENDINGS_CODE = (
@@ -43,6 +44,7 @@ class Renderer(BaseRenderer):
43
44
  "</ol>",
44
45
  "</li>"
45
46
  )
47
+ RE_AMP_LT_GT = re.compile(r'&amp;(lt|gt);')
46
48
 
47
49
  def __init__(self, window=None):
48
50
  super(Renderer, self).__init__(window)
@@ -82,7 +84,7 @@ class Renderer(BaseRenderer):
82
84
  self.parser.reset()
83
85
  try:
84
86
  node.page().runJavaScript("if (typeof window.prepare !== 'undefined') prepare();")
85
- except Exception as e:
87
+ except Exception:
86
88
  pass
87
89
 
88
90
  def on_page_loaded(
@@ -279,7 +281,7 @@ class Renderer(BaseRenderer):
279
281
  self.prev_chunk_replace = False
280
282
  try:
281
283
  self.get_output_node(meta).page().runJavaScript("beginStream();")
282
- except Exception as e:
284
+ except Exception:
283
285
  pass
284
286
 
285
287
  def stream_end(
@@ -304,7 +306,7 @@ class Renderer(BaseRenderer):
304
306
  self.pids[pid].clear()
305
307
  try:
306
308
  self.get_output_node(meta).page().runJavaScript("endStream();")
307
- except Exception as e:
309
+ except Exception:
308
310
  pass
309
311
 
310
312
  def append_context(
@@ -491,6 +493,7 @@ class Renderer(BaseRenderer):
491
493
  buffer_to_parse = buffer
492
494
 
493
495
  html = self.parser.parse(buffer_to_parse)
496
+ del buffer_to_parse
494
497
  is_code_block = html.endswith(self.ENDINGS_CODE)
495
498
  is_list = html.endswith(self.ENDINGS_LIST)
496
499
  is_newline = ("\n" in text_chunk) or buffer.endswith("\n") or is_code_block
@@ -502,14 +505,14 @@ class Renderer(BaseRenderer):
502
505
  else:
503
506
  self.prev_chunk_newline = False
504
507
 
505
- replace_bool = False
508
+ replace = False
506
509
  if is_newline or force_replace or is_list:
507
- replace_bool = True
510
+ replace = True
508
511
  if is_code_block:
509
512
  # don't replace if it is a code block
510
513
  if "\n" not in text_chunk:
511
514
  # if there is no newline in raw_chunk, then don't replace
512
- replace_bool = False
515
+ replace = False
513
516
 
514
517
  if not is_code_block:
515
518
  text_chunk = text_chunk.replace("\n", "<br/>")
@@ -518,25 +521,25 @@ class Renderer(BaseRenderer):
518
521
  # if previous chunk was replaced and current is code block, then add \n to chunk
519
522
  text_chunk = "".join(("\n", text_chunk)) # add newline to chunk
520
523
 
521
- self.prev_chunk_replace = replace_bool
524
+ self.prev_chunk_replace = replace
522
525
 
523
526
  # hide loading spinner if it is the beginning of the text
524
527
  if begin:
525
528
  try:
526
529
  self.get_output_node(meta).page().runJavaScript("hideLoading();")
527
- except Exception as e:
530
+ except Exception:
528
531
  pass
529
532
 
530
533
  # emit chunk to output node
531
534
  try:
532
535
  self.get_output_node(meta).page().bridge.chunk.emit(
533
536
  name_header_str or "",
534
- html if replace_bool else "",
535
- text_chunk if not replace_bool else "",
536
- bool(replace_bool),
537
+ self.sanitize_html(html) if replace else "",
538
+ self.sanitize_html(text_chunk) if not replace else "",
539
+ bool(replace),
537
540
  bool(is_code_block),
538
541
  )
539
- except Exception as e:
542
+ except Exception:
540
543
  pass
541
544
 
542
545
  def next_chunk(
@@ -559,7 +562,7 @@ class Renderer(BaseRenderer):
559
562
  try:
560
563
  self.get_output_node(meta).page().runJavaScript(
561
564
  "nextStream();")
562
- except Exception as e:
565
+ except Exception:
563
566
  pass
564
567
 
565
568
  def append_chunk_input(
@@ -586,9 +589,13 @@ class Renderer(BaseRenderer):
586
589
  self.clear_chunks_input(pid)
587
590
  try:
588
591
  self.get_output_node(meta).page().runJavaScript(
589
- f"appendToInput({self.to_json(self.helpers.format_chunk(text_chunk))});"
592
+ f"""appendToInput({self.to_json(
593
+ self.sanitize_html(
594
+ self.helpers.format_chunk(text_chunk)
595
+ )
596
+ )});"""
590
597
  )
591
- except Exception as e:
598
+ except Exception:
592
599
  pass
593
600
 
594
601
  def append_live(
@@ -628,11 +635,17 @@ class Renderer(BaseRenderer):
628
635
  to_append = self.pids[pid].live_buffer
629
636
  if has_unclosed_code_tag(self.pids[pid].live_buffer):
630
637
  to_append += "\n```"
638
+ print(to_append)
631
639
  try:
632
640
  self.get_output_node(meta).page().runJavaScript(
633
- f"replaceLive({self.to_json(self.parser.parse(to_append))});"
641
+ f"""replaceLive({self.to_json(
642
+ self.sanitize_html(
643
+ self.parser.parse(to_append)
644
+ )
645
+ )});"""
634
646
  )
635
647
  except Exception as e:
648
+ print(e)
636
649
  pass
637
650
 
638
651
  def clear_live(self, meta: CtxMeta, ctx: CtxItem):
@@ -651,7 +664,7 @@ class Renderer(BaseRenderer):
651
664
  js = "clearLive();"
652
665
  try:
653
666
  self.get_output_node_by_pid(pid).page().runJavaScript(js)
654
- except Exception as e:
667
+ except Exception:
655
668
  pass
656
669
 
657
670
  def append_node(
@@ -824,7 +837,11 @@ class Renderer(BaseRenderer):
824
837
  self.append(pid, html)
825
838
  else:
826
839
  try:
827
- self.get_output_node(meta).page().runJavaScript(f"appendExtra('{ctx.id}',{self.to_json(html)});")
840
+ self.get_output_node(meta).page().runJavaScript(
841
+ f"""appendExtra('{ctx.id}',{self.to_json(
842
+ self.sanitize_html(html)
843
+ )});"""
844
+ )
828
845
  except Exception as e:
829
846
  pass
830
847
 
@@ -939,7 +956,7 @@ class Renderer(BaseRenderer):
939
956
  js = "clearInput();"
940
957
  try:
941
958
  self.get_output_node_by_pid(pid).page().runJavaScript(js)
942
- except Exception as e:
959
+ except Exception:
943
960
  pass
944
961
 
945
962
  def clear_chunks_output(
@@ -958,7 +975,7 @@ class Renderer(BaseRenderer):
958
975
  js = "clearOutput();"
959
976
  try:
960
977
  self.get_output_node_by_pid(pid).page().runJavaScript(js)
961
- except Exception as e:
978
+ except Exception:
962
979
  pass
963
980
 
964
981
  def clear_nodes(
@@ -976,7 +993,7 @@ class Renderer(BaseRenderer):
976
993
  js = "clearNodes();"
977
994
  try:
978
995
  self.get_output_node_by_pid(pid).page().runJavaScript(js)
979
- except Exception as e:
996
+ except Exception:
980
997
  pass
981
998
 
982
999
  def prepare_node(
@@ -1174,9 +1191,11 @@ class Renderer(BaseRenderer):
1174
1191
  """
1175
1192
  try:
1176
1193
  self.get_output_node_by_pid(pid).page().runJavaScript(
1177
- f"if (typeof window.appendNode !== 'undefined') appendNode({self.to_json(html)});"
1194
+ f"""if (typeof window.appendNode !== 'undefined') appendNode({self.to_json(
1195
+ self.sanitize_html(html)
1196
+ )});"""
1178
1197
  )
1179
- except Exception as e:
1198
+ except Exception:
1180
1199
  pass
1181
1200
 
1182
1201
  def reload(self):
@@ -1266,7 +1285,7 @@ class Renderer(BaseRenderer):
1266
1285
  try:
1267
1286
  self.get_output_node(ctx.meta).page().runJavaScript(
1268
1287
  f"if (typeof window.removeNode !== 'undefined') removeNode({self.to_json(ctx.id)});")
1269
- except Exception as e:
1288
+ except Exception:
1270
1289
  pass
1271
1290
 
1272
1291
  def remove_items_from(self, ctx: CtxItem):
@@ -1278,7 +1297,7 @@ class Renderer(BaseRenderer):
1278
1297
  try:
1279
1298
  self.get_output_node(ctx.meta).page().runJavaScript(
1280
1299
  f"if (typeof window.removeNodesFromId !== 'undefined') removeNodesFromId({self.to_json(ctx.id)});")
1281
- except Exception as e:
1300
+ except Exception:
1282
1301
  pass
1283
1302
 
1284
1303
  def reset_names(self, meta: CtxMeta):
@@ -1329,7 +1348,7 @@ class Renderer(BaseRenderer):
1329
1348
  nodes = self.get_all_nodes()
1330
1349
  for node in nodes:
1331
1350
  node.page().runJavaScript("if (typeof window.enableEditIcons !== 'undefined') enableEditIcons();")
1332
- except Exception as e:
1351
+ except Exception:
1333
1352
  pass
1334
1353
 
1335
1354
  def on_disable_edit(self, live: bool = True):
@@ -1344,7 +1363,7 @@ class Renderer(BaseRenderer):
1344
1363
  nodes = self.get_all_nodes()
1345
1364
  for node in nodes:
1346
1365
  node.page().runJavaScript("if (typeof window.disableEditIcons !== 'undefined') disableEditIcons();")
1347
- except Exception as e:
1366
+ except Exception:
1348
1367
  pass
1349
1368
 
1350
1369
  def on_enable_timestamp(self, live: bool = True):
@@ -1359,7 +1378,7 @@ class Renderer(BaseRenderer):
1359
1378
  nodes = self.get_all_nodes()
1360
1379
  for node in nodes:
1361
1380
  node.page().runJavaScript("if (typeof window.enableTimestamp !== 'undefined') enableTimestamp();")
1362
- except Exception as e:
1381
+ except Exception:
1363
1382
  pass
1364
1383
 
1365
1384
  def on_disable_timestamp(self, live: bool = True):
@@ -1374,7 +1393,7 @@ class Renderer(BaseRenderer):
1374
1393
  nodes = self.get_all_nodes()
1375
1394
  for node in nodes:
1376
1395
  node.page().runJavaScript("if (typeof window.disableTimestamp !== 'undefined') disableTimestamp();")
1377
- except Exception as e:
1396
+ except Exception:
1378
1397
  pass
1379
1398
 
1380
1399
  def update_names(
@@ -1469,9 +1488,11 @@ class Renderer(BaseRenderer):
1469
1488
  """
1470
1489
  try:
1471
1490
  self.get_output_node(meta).page().runJavaScript(
1472
- f"if (typeof window.appendToolOutput !== 'undefined') appendToolOutput({self.to_json(content)});"
1491
+ f"""if (typeof window.appendToolOutput !== 'undefined') appendToolOutput({self.to_json(
1492
+ self.sanitize_html(content)
1493
+ )});"""
1473
1494
  )
1474
- except Exception as e:
1495
+ except Exception:
1475
1496
  pass
1476
1497
 
1477
1498
  def tool_output_update(
@@ -1487,9 +1508,11 @@ class Renderer(BaseRenderer):
1487
1508
  """
1488
1509
  try:
1489
1510
  self.get_output_node(meta).page().runJavaScript(
1490
- f"if (typeof window.updateToolOutput !== 'undefined') updateToolOutput({self.to_json(content)});"
1511
+ f"""if (typeof window.updateToolOutput !== 'undefined') updateToolOutput({self.to_json(
1512
+ self.sanitize_html(content)
1513
+ )});"""
1491
1514
  )
1492
- except Exception as e:
1515
+ except Exception:
1493
1516
  pass
1494
1517
 
1495
1518
  def tool_output_clear(self, meta: CtxMeta):
@@ -1502,7 +1525,7 @@ class Renderer(BaseRenderer):
1502
1525
  self.get_output_node(meta).page().runJavaScript(
1503
1526
  f"if (typeof window.clearToolOutput !== 'undefined') clearToolOutput();"
1504
1527
  )
1505
- except Exception as e:
1528
+ except Exception:
1506
1529
  pass
1507
1530
 
1508
1531
  def tool_output_begin(self, meta: CtxMeta):
@@ -1515,7 +1538,7 @@ class Renderer(BaseRenderer):
1515
1538
  self.get_output_node(meta).page().runJavaScript(
1516
1539
  f"if (typeof window.beginToolOutput !== 'undefined') beginToolOutput();"
1517
1540
  )
1518
- except Exception as e:
1541
+ except Exception:
1519
1542
  pass
1520
1543
 
1521
1544
  def tool_output_end(self):
@@ -1524,9 +1547,20 @@ class Renderer(BaseRenderer):
1524
1547
  self.get_output_node().page().runJavaScript(
1525
1548
  f"if (typeof window.endToolOutput !== 'undefined') endToolOutput();"
1526
1549
  )
1527
- except Exception as e:
1550
+ except Exception:
1528
1551
  pass
1529
1552
 
1553
+ def sanitize_html(self, html: str) -> str:
1554
+ """
1555
+ Sanitize HTML to prevent XSS attacks
1556
+
1557
+ :param html: HTML string to sanitize
1558
+ :return: sanitized HTML string
1559
+ """
1560
+ if not html:
1561
+ return ""
1562
+ return self.RE_AMP_LT_GT.sub(r'&\1;', html)
1563
+
1530
1564
  def append_debug(
1531
1565
  self,
1532
1566
  ctx: CtxItem,
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.10",
4
- "app.version": "2.6.10",
5
- "updated_at": "2025-08-17T00:00:00"
3
+ "version": "2.6.11",
4
+ "app.version": "2.6.11",
5
+ "updated_at": "2025-08-18T00: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.10",
4
- "app.version": "2.6.10",
5
- "updated_at": "2025-08-17T23:07:35"
3
+ "version": "2.6.11",
4
+ "app.version": "2.6.11",
5
+ "updated_at": "2025-08-18T23:07:35"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -21,7 +21,7 @@
21
21
  "function": []
22
22
  },
23
23
  "experts": [],
24
- "idx": "base",
24
+ "idx": "_",
25
25
  "agent_provider": "openai",
26
26
  "assistant_id": "",
27
27
  "enabled": true,
@@ -21,7 +21,7 @@
21
21
  "function": []
22
22
  },
23
23
  "experts": [],
24
- "idx": "base",
24
+ "idx": "_",
25
25
  "agent_provider": "openai_assistant",
26
26
  "assistant_id": "",
27
27
  "enabled": true,
@@ -21,7 +21,7 @@
21
21
  "function": []
22
22
  },
23
23
  "experts": [],
24
- "idx": "base",
24
+ "idx": "_",
25
25
  "agent_provider": "planner",
26
26
  "assistant_id": "",
27
27
  "enabled": true,
@@ -21,7 +21,7 @@
21
21
  "function": []
22
22
  },
23
23
  "experts": [],
24
- "idx": "base",
24
+ "idx": "_",
25
25
  "agent_provider": "react",
26
26
  "assistant_id": "",
27
27
  "enabled": true,
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.18 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -247,7 +247,9 @@ class MainWindow(QMainWindow, QtStyleTools):
247
247
 
248
248
  :param message: status message
249
249
  """
250
- self.dispatch(KernelEvent(KernelEvent.STATUS, {"status": str(message)}))
250
+ message = message if isinstance(message, str) else str(message)
251
+ self.dispatch(KernelEvent(KernelEvent.STATUS, {"status": message}))
252
+ del message # free memory
251
253
 
252
254
  @Slot(str)
253
255
  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.16 00:00:00 #
9
+ # Updated Date: 2025.08.18 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer
@@ -311,7 +311,7 @@ class ChatWebOutput(QWebEngineView):
311
311
 
312
312
  # audio read (get text only on click, don't copy immediately)
313
313
  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()))
314
+ action.triggered.connect(self._read_selected_text)
315
315
  menu.addAction(action)
316
316
 
317
317
  # copy to
@@ -320,7 +320,7 @@ class ChatWebOutput(QWebEngineView):
320
320
 
321
321
  # save as (selected) - get selection at the moment of click
322
322
  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'))
323
+ action.triggered.connect(self._save_selected_txt)
324
324
  menu.addAction(action)
325
325
  else:
326
326
  # select all
@@ -344,6 +344,20 @@ class ChatWebOutput(QWebEngineView):
344
344
 
345
345
  menu.exec_(self.mapToGlobal(position))
346
346
 
347
+ @Slot()
348
+ def _save_selected_txt(self):
349
+ """Save selected content as text file"""
350
+ self.signals.save_as.emit(self.get_selected_text(), 'txt')
351
+
352
+ @Slot()
353
+ def _read_selected_text(self):
354
+ """
355
+ Read selected text using text-to-speech
356
+ """
357
+ selected_text = self.get_selected_text()
358
+ if selected_text:
359
+ self.signals.audio_read.emit(selected_text)
360
+
347
361
  @Slot()
348
362
  def _save_as_text(self):
349
363
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.10
3
+ Version: 2.6.11
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.11** | build: **2025-08-18** | Python: **>=3.10, <3.14**
112
112
 
113
113
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
114
114
  >
@@ -4566,6 +4566,12 @@ may consume additional tokens that are not displayed in the main window.
4566
4566
 
4567
4567
  ## Recent changes:
4568
4568
 
4569
+ **2.6.11 (2025-08-18)**
4570
+
4571
+ - Added the ability to close the dialog window with the Esc key.
4572
+ - Made context item deletion without output refresh.
4573
+ - Optimizations.
4574
+
4569
4575
  **2.6.10 (2025-08-17)**
4570
4576
 
4571
4577
  - Enhanced the handling of the context list.