pygpt-net 2.6.5__py3-none-any.whl → 2.6.7__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,11 @@
1
+ 2.6.7 (2025-08-16)
2
+
3
+ - Fix: missing entity sanitize.
4
+
5
+ 2.6.6 (2025-08-16)
6
+
7
+ - Output rendering optimization.
8
+
1
9
  2.6.5 (2025-08-16)
2
10
 
3
11
  - Fix: crash when creating a context in a new group.
pygpt_net/__init__.py CHANGED
@@ -13,7 +13,7 @@ __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.5"
16
+ __version__ = "2.6.7"
17
17
  __build__ = "2025-08-16"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
pygpt_net/app.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 00:00:00 #
9
+ # Updated Date: 2025.08.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -22,8 +22,13 @@ if platform.system() == 'Windows':
22
22
  # fix ffmpeg bug: [SWR] Output channel layout "" is invalid or unsupported.
23
23
  os.environ['QT_MEDIA_BACKEND'] = 'windows'
24
24
 
25
+ elif platform.system() == 'Linux':
26
+ os.environ.setdefault("MALLOC_ARENA_MAX", "2") # 2 arenas
27
+ os.environ.setdefault("MALLOC_TRIM_THRESHOLD_", "131072") # 128 KiB
28
+
25
29
  # enable debug logging
26
30
  # os.environ["QT_LOGGING_RULES"] = "*.debug=true"
31
+ # os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "9222"
27
32
 
28
33
  _original_open = builtins.open
29
34
 
@@ -6,10 +6,11 @@
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.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
13
+ import gc
13
14
  import io
14
15
  from typing import Optional, Literal
15
16
 
@@ -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.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -47,11 +47,10 @@ class Body:
47
47
  let scrollTimeout = null;
48
48
  let prevScroll = 0;
49
49
  let bridge;
50
+ let streamQ = [];
51
+ let streamRAF = 0;
50
52
  let pid = """
51
- _HTML_P2 = """
52
- new QWebChannel(qt.webChannelTransport, function (channel) {
53
- bridge = channel.objects.bridge;
54
- });
53
+ _HTML_P2 = """
55
54
  let collapsed_idx = [];
56
55
  let domOutputStream = document.getElementById('_append_output_');
57
56
  let domOutput = document.getElementById('_output_');
@@ -68,6 +67,7 @@ class Body:
68
67
  let pendingHighlightRoot = null;
69
68
  let pendingHighlightMath = false;
70
69
  let scrollScheduled = false;
70
+ const AMP_LT_GT = /&(lt|gt);/g;
71
71
 
72
72
  history.scrollRestoration = "manual";
73
73
  document.addEventListener('keydown', function(event) {
@@ -213,7 +213,8 @@ class Body:
213
213
  prevScroll = el.scrollHeight;
214
214
  }
215
215
  function sanitize(content) {
216
- return content.replace(/</g, '<').replace(/>/g, '>');
216
+ if (content.indexOf('&') === -1) return content;
217
+ return content.replace(AMP_LT_GT, '&$1;'); // < -> <, > -> >
217
218
  }
218
219
  function appendToInput(content) {
219
220
  const element = els.appendInput || document.getElementById('_append_input_');
@@ -350,11 +351,22 @@ class Body:
350
351
  function endStream() {
351
352
  clearOutput();
352
353
  }
354
+ function enqueueStream(name_header, content, chunk, replace = false, is_code_block = false) {
355
+ streamQ.push({name_header, content, chunk, replace, is_code_block});
356
+ if (!streamRAF) {
357
+ streamRAF = requestAnimationFrame(drainStream);
358
+ }
359
+ }
360
+ function drainStream() {
361
+ streamRAF = 0;
362
+ while (streamQ.length) {
363
+ const {name_header, content, chunk, replace, is_code_block} = streamQ.shift();
364
+ appendStream(name_header, content, chunk, replace, is_code_block);
365
+ }
366
+ }
353
367
  function appendStream(name_header, content, chunk, replace = false, is_code_block = false) {
354
368
  hideTips();
355
369
  const element = getStreamContainer();
356
- let doHighlight = true;
357
- let doMath = true;
358
370
  let msg;
359
371
  if (element) {
360
372
  let box = element.querySelector('.msg-box');
@@ -378,7 +390,15 @@ class Body:
378
390
  }
379
391
  if (msg) {
380
392
  if (replace) {
381
- msg.innerHTML = sanitize(content);
393
+ msg.replaceChildren();
394
+ if (content) {
395
+ msg.insertAdjacentHTML('afterbegin', sanitize(content));
396
+ }
397
+ let doMath = true;
398
+ if (!is_code_block) {
399
+ doMath = false;
400
+ }
401
+ highlightCode(doMath, msg);
382
402
  domLastCodeBlock = null;
383
403
  domLastParagraphBlock = null;
384
404
  } else {
@@ -393,47 +413,31 @@ class Body:
393
413
  }
394
414
  }
395
415
  if (lastCodeBlock) {
396
- lastCodeBlock.insertAdjacentHTML('beforeend', chunk);
416
+ lastCodeBlock.insertAdjacentHTML('beforeend', sanitize(chunk));
397
417
  domLastCodeBlock = lastCodeBlock;
398
- doHighlight = false;
399
418
  } else {
400
- msg.insertAdjacentHTML('beforeend', chunk);
419
+ msg.insertAdjacentHTML('beforeend', sanitize(chunk));
401
420
  domLastCodeBlock = null;
402
421
  }
403
- doMath = false;
404
422
  } else {
405
423
  domLastCodeBlock = null;
406
- if (msg.innerHTML.trim().endsWith('</p>')) {
407
- let lastParagraphBlock;
408
- if (domLastParagraphBlock) {
409
- lastParagraphBlock = domLastParagraphBlock;
410
- } else {
411
- const blocksParagraph = msg.querySelectorAll('p');
412
- if (blocksParagraph.length > 0) {
413
- lastParagraphBlock = blocksParagraph[blocksParagraph.length - 1];
414
- }
415
- }
416
- if (lastParagraphBlock) {
417
- domLastParagraphBlock = lastParagraphBlock;
418
- lastParagraphBlock.insertAdjacentHTML('beforeend', chunk);
419
- } else {
420
- domLastParagraphBlock = null;
421
- msg.insertAdjacentHTML('beforeend', chunk);
422
- }
424
+ let p = (domLastParagraphBlock && msg.contains(domLastParagraphBlock))
425
+ ? domLastParagraphBlock
426
+ : (msg.lastElementChild && msg.lastElementChild.tagName === 'P'
427
+ ? msg.lastElementChild
428
+ : null);
429
+ if (p) {
430
+ p.insertAdjacentHTML('beforeend', sanitize(chunk));
431
+ domLastParagraphBlock = p;
423
432
  } else {
424
- domLastParagraphBlock = null;
425
- msg.insertAdjacentHTML('beforeend', chunk);
433
+ msg.insertAdjacentHTML('beforeend', sanitize(chunk));
434
+ const last = msg.lastElementChild;
435
+ domLastParagraphBlock = (last && last.tagName === 'P') ? last : null;
426
436
  }
427
- doHighlight = false;
428
437
  }
429
438
  }
430
439
  }
431
440
  }
432
- if (replace) {
433
- if (doHighlight) {
434
- highlightCode(doMath, msg);
435
- }
436
- }
437
441
  scheduleScroll(true);
438
442
  }
439
443
  function replaceOutput(name_header, content) {
@@ -461,7 +465,8 @@ class Body:
461
465
  msg = box.querySelector('.msg');
462
466
  }
463
467
  if (msg) {
464
- msg.innerHTML = sanitize(content);
468
+ msg.replaceChildren();
469
+ msg.insertAdjacentHTML('afterbegin', sanitize(content));
465
470
  highlightCode(true, msg);
466
471
  scheduleScroll();
467
472
  }
@@ -756,6 +761,13 @@ class Body:
756
761
  }
757
762
  }
758
763
  document.addEventListener('DOMContentLoaded', function() {
764
+ new QWebChannel(qt.webChannelTransport, function (channel) {
765
+ bridge = channel.objects.bridge;
766
+ bridge.chunk.connect((name, html, chunk, replace, isCode) => {
767
+ appendStream(name, html, chunk, replace, isCode);
768
+ });
769
+ if (bridge.js_ready) bridge.js_ready();
770
+ });
759
771
  initDomRefs();
760
772
  const container = els.container;
761
773
  function addClassToMsg(id, className) {
@@ -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.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -33,6 +33,16 @@ from pygpt_net.core.events import RenderEvent
33
33
  class Renderer(BaseRenderer):
34
34
  NODE_INPUT = 0
35
35
  NODE_OUTPUT = 1
36
+ ENDINGS_CODE = (
37
+ "</code></pre></div>",
38
+ "</code></pre></div><br/>",
39
+ "</code></pre></div><br>"
40
+ )
41
+ ENDINGS_LIST = (
42
+ "</ul>",
43
+ "</ol>",
44
+ "</li>"
45
+ )
36
46
 
37
47
  def __init__(self, window=None):
38
48
  super(Renderer, self).__init__(window)
@@ -71,7 +81,7 @@ class Renderer(BaseRenderer):
71
81
  self.reset(meta)
72
82
  self.parser.reset()
73
83
  try:
74
- node.page().runJavaScript(f"if (typeof window.prepare !== 'undefined') prepare();")
84
+ node.page().runJavaScript("if (typeof window.prepare !== 'undefined') prepare();")
75
85
  except Exception as e:
76
86
  pass
77
87
 
@@ -167,7 +177,7 @@ class Renderer(BaseRenderer):
167
177
  node = self.get_output_node_by_pid(pid)
168
178
  try:
169
179
  node.page().runJavaScript(
170
- f"if (typeof window.showLoading !== 'undefined') showLoading();")
180
+ "if (typeof window.showLoading !== 'undefined') showLoading();")
171
181
  except Exception as e:
172
182
  pass
173
183
 
@@ -177,7 +187,7 @@ class Renderer(BaseRenderer):
177
187
  if node is not None:
178
188
  try:
179
189
  node.page().runJavaScript(
180
- f"if (typeof window.hideLoading !== 'undefined') hideLoading();")
190
+ "if (typeof window.hideLoading !== 'undefined') hideLoading();")
181
191
  except Exception as e:
182
192
  pass
183
193
 
@@ -187,7 +197,7 @@ class Renderer(BaseRenderer):
187
197
  if node is not None:
188
198
  try:
189
199
  node.page().runJavaScript(
190
- f"if (typeof window.hideLoading !== 'undefined') hideLoading();")
200
+ "if (typeof window.hideLoading !== 'undefined') hideLoading();")
191
201
  except Exception as e:
192
202
  pass
193
203
 
@@ -410,7 +420,7 @@ class Renderer(BaseRenderer):
410
420
  output = ctx.output
411
421
  if isinstance(ctx.extra, dict) and ctx.extra.get("output"):
412
422
  if self.window.core.config.get("llama.idx.chat.agent.render.all", False):
413
- output = "__agent_begin__" + (ctx.output or "") + "__agent_end__" + ctx.extra["output"]
423
+ output = f"__agent_begin__{ctx.output}__agent_end__{ctx.extra['output']}"
414
424
  else:
415
425
  output = ctx.extra["output"]
416
426
  else:
@@ -450,21 +460,20 @@ class Renderer(BaseRenderer):
450
460
 
451
461
  name_header_str = self.get_name_header(ctx)
452
462
  self.update_names(meta, ctx)
453
- raw_chunk = text_chunk if isinstance(text_chunk, str) else str(text_chunk)
454
- raw_chunk = raw_chunk.translate({ord('<'): '&lt;', ord('>'): '&gt;'})
463
+ text_chunk = text_chunk if isinstance(text_chunk, str) else str(text_chunk)
464
+ text_chunk = text_chunk.translate({ord('<'): '&lt;', ord('>'): '&gt;'})
455
465
 
456
466
  if begin:
457
- debug = ""
458
467
  if self.is_debug():
459
468
  debug = self.append_debug(ctx, pid, "stream")
460
- if debug:
461
- raw_chunk = debug + raw_chunk
469
+ if debug:
470
+ text_chunk = debug + text_chunk
462
471
  pctx.clear() # reset buffer
463
472
  pctx.is_cmd = False # reset command flag
464
473
  self.clear_chunks_output(pid)
465
474
  self.prev_chunk_replace = False
466
475
 
467
- pctx.append_buffer(raw_chunk)
476
+ pctx.append_buffer(text_chunk)
468
477
 
469
478
  buffer = pctx.buffer
470
479
  if has_unclosed_code_tag(buffer):
@@ -473,17 +482,13 @@ class Renderer(BaseRenderer):
473
482
  buffer_to_parse = buffer
474
483
 
475
484
  html = self.parser.parse(buffer_to_parse)
476
- is_code_block = html.endswith((
477
- "</code></pre></div>",
478
- "</code></pre></div><br/>",
479
- "</code></pre></div><br>"
480
- ))
481
- is_list = html.endswith(("</ul>", "</ol>", "</li>"))
482
- is_newline = ("\n" in raw_chunk) or buffer.endswith("\n") or is_code_block
485
+ is_code_block = html.endswith(self.ENDINGS_CODE)
486
+ is_list = html.endswith(self.ENDINGS_LIST)
487
+ is_newline = ("\n" in text_chunk) or buffer.endswith("\n") or is_code_block
483
488
  force_replace = False
484
489
  if self.prev_chunk_newline:
485
490
  force_replace = True
486
- if "\n" in raw_chunk:
491
+ if "\n" in text_chunk:
487
492
  self.prev_chunk_newline = True
488
493
  else:
489
494
  self.prev_chunk_newline = False
@@ -493,36 +498,25 @@ class Renderer(BaseRenderer):
493
498
  replace_bool = True
494
499
  if is_code_block:
495
500
  # don't replace if it is a code block
496
- if "\n" not in raw_chunk:
501
+ if "\n" not in text_chunk:
497
502
  # if there is no newline in raw_chunk, then don't replace
498
503
  replace_bool = False
499
504
 
500
- code_block_arg = "true" if is_code_block else "false"
501
505
  if not is_code_block:
502
- out_chunk = raw_chunk.replace("\n", "<br/>")
506
+ text_chunk = text_chunk.replace("\n", "<br/>")
503
507
  else:
504
- out_chunk = raw_chunk
505
- if self.prev_chunk_replace and not has_unclosed_code_tag(raw_chunk):
508
+ if self.prev_chunk_replace and not has_unclosed_code_tag(text_chunk):
506
509
  # if previous chunk was replaced and current is code block, then add \n to chunk
507
- out_chunk = "".join(("\n", out_chunk)) # add newline to chunk
508
-
509
- escaped_chunk = json.dumps(out_chunk)
510
- if name_header_str:
511
- name_header = json.dumps(name_header_str)
512
- else:
513
- name_header = '""'
514
- replace = "true" if replace_bool else "false"
515
-
516
- if replace_bool:
517
- escaped_buffer = json.dumps(html)
518
- else:
519
- escaped_buffer = '""'
510
+ text_chunk = "".join(("\n", text_chunk)) # add newline to chunk
520
511
 
521
512
  self.prev_chunk_replace = replace_bool
522
-
523
513
  try:
524
- self.get_output_node(meta).page().runJavaScript(
525
- f"appendStream({name_header}, {escaped_buffer}, {escaped_chunk}, {replace}, {code_block_arg});"
514
+ self.get_output_node(meta).page().bridge.chunk.emit(
515
+ name_header_str or "",
516
+ html if replace_bool else "",
517
+ text_chunk if not replace_bool else "",
518
+ bool(replace_bool),
519
+ bool(is_code_block),
526
520
  )
527
521
  except Exception as e:
528
522
  pass
@@ -546,7 +540,7 @@ class Renderer(BaseRenderer):
546
540
  self.prev_chunk_newline = False
547
541
  try:
548
542
  self.get_output_node(meta).page().runJavaScript(
549
- f"nextStream();")
543
+ "nextStream();")
550
544
  except Exception as e:
551
545
  pass
552
546
 
@@ -780,7 +774,7 @@ class Renderer(BaseRenderer):
780
774
  except Exception as e:
781
775
  pass
782
776
  if files_html:
783
- html_parts.append("<br/>" + "<br/>".join(files_html))
777
+ html_parts.append("<br/><br/>".join(files_html))
784
778
 
785
779
  c = len(ctx.urls)
786
780
  if c > 0:
@@ -797,7 +791,7 @@ class Renderer(BaseRenderer):
797
791
  except Exception as e:
798
792
  pass
799
793
  if urls_html:
800
- html_parts.append("<br/>" + "<br/>".join(urls_html))
794
+ html_parts.append("<br/><br/>".join(urls_html))
801
795
 
802
796
  if self.window.core.config.get('ctx.sources'):
803
797
  if ctx.doc_ids is not None and len(ctx.doc_ids) > 0:
@@ -814,7 +808,7 @@ class Renderer(BaseRenderer):
814
808
  else:
815
809
  escaped_html = json.dumps(html)
816
810
  try:
817
- self.get_output_node(meta).page().runJavaScript("appendExtra('{}',{});".format(ctx.id, escaped_html))
811
+ self.get_output_node(meta).page().runJavaScript(f"appendExtra('{ctx.id}',{escaped_html});")
818
812
  except Exception as e:
819
813
  pass
820
814
 
@@ -843,7 +837,7 @@ class Renderer(BaseRenderer):
843
837
  if timestamp is not None:
844
838
  ts = datetime.fromtimestamp(timestamp)
845
839
  hour = ts.strftime("%H:%M:%S")
846
- text = '<span class="ts">{}: </span>{}'.format(hour, text)
840
+ text = f'<span class="ts">{hour}: </span>{text}'
847
841
  return text
848
842
 
849
843
  def reset(
@@ -1031,7 +1025,7 @@ class Renderer(BaseRenderer):
1031
1025
  self.helpers.format_user_text(html),
1032
1026
  type=self.NODE_INPUT
1033
1027
  )
1034
- html = "<p>" + content + "</p>"
1028
+ html = f"<p>{content}</p>"
1035
1029
  html = self.helpers.post_format_text(html)
1036
1030
  name = self.pids[pid].name_user
1037
1031
 
@@ -1086,7 +1080,7 @@ class Renderer(BaseRenderer):
1086
1080
  (len(ctx.cmds) > 0 or (ctx.extra_ctx is not None and len(ctx.extra_ctx) > 0))
1087
1081
  )
1088
1082
  pid = self.get_or_create_pid(meta)
1089
- msg_id = "msg-bot-" + str(ctx.id) if ctx is not None else ""
1083
+ msg_id = f"msg-bot-{ctx.id}" if ctx is not None else ""
1090
1084
  html = self.helpers.pre_format_text(html)
1091
1085
  html = self.parser.parse(html)
1092
1086
  html = self.append_timestamp(ctx, html, type=self.NODE_OUTPUT)
@@ -1099,7 +1093,7 @@ class Renderer(BaseRenderer):
1099
1093
  output_class = "display:none"
1100
1094
  cmd_icon = f'<img src="{self._file_prefix}{self._icon_expand}" width="25" height="25" valign="middle">'
1101
1095
  expand_btn = (
1102
- f"<span class='toggle-cmd-output' onclick='toggleToolOutput({str(ctx.id)});' title='{trans('action.cmd.expand')}' "
1096
+ f"<span class='toggle-cmd-output' onclick='toggleToolOutput({ctx.id});' title='{trans('action.cmd.expand')}' "
1103
1097
  f"role='button'>{cmd_icon}</span>"
1104
1098
  )
1105
1099
 
@@ -1116,15 +1110,12 @@ class Renderer(BaseRenderer):
1116
1110
  and isinstance(ctx.extra, dict) and "agent_step" in ctx.extra:
1117
1111
  tool_output = self.helpers.format_cmd_text(str(ctx.input))
1118
1112
  else:
1119
- if (
1120
- next_ctx is None and
1121
- (
1122
- ctx.output.startswith("<tool>{\"cmd\"") or
1123
- ctx.output.strip().endswith("}</tool>") or
1124
- ctx.output.startswith("&lt;tool&gt;{\"cmd\"") or
1125
- ctx.output.strip().endswith("}&lt;/tool&gt;") or
1126
- len(ctx.cmds) > 0
1127
- )
1113
+ out = (getattr(ctx, "output", "") or "")
1114
+ cmds = getattr(ctx, "cmds", ())
1115
+ if next_ctx is None and (
1116
+ cmds
1117
+ or out.startswith(('<tool>{"cmd"', '&lt;tool&gt;{"cmd"'))
1118
+ or out.rstrip().endswith(('}</tool>', '}&lt;/tool&gt;'))
1128
1119
  ):
1129
1120
  spinner_class = "" if ctx.live else "display:none"
1130
1121
  spinner = (
@@ -1196,7 +1187,7 @@ class Renderer(BaseRenderer):
1196
1187
 
1197
1188
  if not output_name and not avatar_html:
1198
1189
  return ""
1199
- return "<div class=\"name-header name-bot\">" + avatar_html + output_name + "</div>"
1190
+ return f"<div class=\"name-header name-bot\">{avatar_html}{output_name}</div>"
1200
1191
 
1201
1192
  def flush_output(
1202
1193
  self,
@@ -1234,7 +1225,6 @@ class Renderer(BaseRenderer):
1234
1225
  return
1235
1226
 
1236
1227
  html = self.body.get_html(pid)
1237
- self.pids[pid].document = html
1238
1228
  node = self.get_output_node_by_pid(pid)
1239
1229
  if node is not None:
1240
1230
  node.setHtml(html, baseUrl="file://")
@@ -1253,9 +1243,12 @@ class Renderer(BaseRenderer):
1253
1243
  return
1254
1244
  html = self.body.get_html(pid)
1255
1245
  self.pids[pid].loaded = False
1256
- self.pids[pid].document = html
1257
1246
  node = self.get_output_node_by_pid(pid)
1258
1247
  if node is not None:
1248
+ # hard reset
1249
+ # old_view = node
1250
+ # new_view = old_view.hard_reset()
1251
+ # self.window.ui.nodes['output'][pid] = new_view
1259
1252
  node.resetPage()
1260
1253
  node.setHtml(html, baseUrl="file://")
1261
1254
 
@@ -1291,25 +1284,6 @@ class Renderer(BaseRenderer):
1291
1284
  """
1292
1285
  return self.window.ui.nodes['input']
1293
1286
 
1294
- def get_document(
1295
- self,
1296
- plain: bool = False
1297
- ):
1298
- """
1299
- Get document content (plain or HTML)
1300
-
1301
- :param plain: True to convert to plain text
1302
- :return: document content
1303
- """
1304
- pid = self.window.core.ctx.container.get_active_pid()
1305
- if pid is None:
1306
- return ""
1307
- if plain:
1308
- return self.parser.to_plain_text(
1309
- self.pids[pid].document.replace("<br>", "\n").replace("<br/>", "\n")
1310
- )
1311
- return self.pids[pid].document
1312
-
1313
1287
  def remove_item(self, ctx: CtxItem):
1314
1288
  """
1315
1289
  Remove item from output
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.5",
4
- "app.version": "2.6.5",
3
+ "version": "2.6.7",
4
+ "app.version": "2.6.7",
5
5
  "updated_at": "2025-08-16T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.5",
4
- "app.version": "2.6.5",
3
+ "version": "2.6.7",
4
+ "app.version": "2.6.7",
5
5
  "updated_at": "2025-08-16T23:07:35"
6
6
  },
7
7
  "items": {
pygpt_net/ui/main.py CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import os
13
13
 
14
- from PySide6.QtCore import QTimer, Signal, Slot, QThreadPool, QEvent, Qt, QLoggingCategory
14
+ from PySide6.QtCore import QTimer, Signal, Slot, QThreadPool, QEvent, Qt, QLoggingCategory, QEventLoop
15
15
  from PySide6.QtGui import QShortcut, QKeySequence
16
16
  from qasync import QApplication
17
17
  from PySide6.QtWidgets import QMainWindow
@@ -22,6 +22,7 @@ from pygpt_net.container import Container
22
22
  from pygpt_net.controller import Controller
23
23
  from pygpt_net.tools import Tools
24
24
  from pygpt_net.ui import UI
25
+ from pygpt_net.ui.widget.textarea.web import ChatWebOutput
25
26
  from pygpt_net.utils import get_app_meta
26
27
 
27
28
 
@@ -280,6 +281,17 @@ class MainWindow(QMainWindow, QtStyleTools):
280
281
  event.ignore()
281
282
  self.hide()
282
283
  return
284
+
285
+ for view in self.findChildren(ChatWebOutput):
286
+ try:
287
+ view.on_delete()
288
+ except Exception:
289
+ pass
290
+
291
+ for _ in range(6):
292
+ QApplication.sendPostedEvents(None, QEvent.DeferredDelete)
293
+ QApplication.processEvents(QEventLoop.AllEvents, 50)
294
+
283
295
  self.shutdown()
284
296
  event.accept()
285
297
 
@@ -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.08.15 23:00:00 #
9
+ # Updated Date: 2025.08.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent
12
+ from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer
13
13
  from PySide6.QtWebChannel import QWebChannel
14
14
  from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage, QWebEngineProfile
15
15
  from PySide6.QtWebEngineWidgets import QWebEngineView
16
16
  from PySide6.QtGui import QAction, QIcon
17
- from PySide6.QtWidgets import QMenu
17
+ from PySide6.QtWidgets import QMenu, QApplication
18
18
 
19
19
  from pygpt_net.core.events import RenderEvent
20
20
  from pygpt_net.item.ctx import CtxMeta
@@ -45,24 +45,46 @@ class ChatWebOutput(QWebEngineView):
45
45
  self.html_content = ""
46
46
  self.meta = None
47
47
  self.tab = None
48
+ self.setProperty('class', 'layout-output-web')
49
+
48
50
  self._glwidget = None
49
51
  self._glwidget_filter_installed = False
50
- self.setProperty('class', 'layout-output-web')
52
+ self._profile = self._create_profile(parent=self)
51
53
 
52
- prof = self.page().profile()
53
- try:
54
- prof.setOffTheRecord(True)
55
- except Exception:
56
- pass
54
+ self.setPage(CustomWebEnginePage(self.window, self, profile=self._profile))
55
+
56
+ def _detach_gl_event_filter(self):
57
+ """
58
+ Detach OpenGL widget event filter if installed
59
+ """
60
+ if self._glwidget and self._glwidget_filter_installed:
61
+ try:
62
+ self._glwidget.removeEventFilter(self)
63
+ except Exception:
64
+ pass
65
+ self._glwidget = None
66
+ self._glwidget_filter_installed = False
67
+
68
+ def _release_profile_after_page(self, page, profile):
69
+ """
70
+ Release profile after page is destroyed
71
+
72
+ :param page: QWebEnginePage - page to check
73
+ :param profile: QWebEngineProfile - profile to release
74
+ """
75
+ if not profile or profile is QWebEngineProfile.defaultProfile():
76
+ return
77
+ if page:
78
+ try:
79
+ page.destroyed.connect(profile.deleteLater)
80
+ return
81
+ except Exception:
82
+ pass
57
83
  try:
58
- prof.setHttpCacheType(QWebEngineProfile.NoCache)
59
- prof.setHttpCacheMaximumSize(0)
60
- prof.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
84
+ profile.deleteLater()
61
85
  except Exception:
62
86
  pass
63
87
 
64
- self.setPage(CustomWebEnginePage(self.window, self, profile=prof))
65
-
66
88
  def _teardown_page(self, page: QWebEnginePage):
67
89
  """
68
90
  Teardown page to clean up resources
@@ -71,7 +93,6 @@ class ChatWebOutput(QWebEngineView):
71
93
  """
72
94
  if not page:
73
95
  return
74
-
75
96
  try:
76
97
  # detach the channel from the page to break JS<->Python references
77
98
  page.setWebChannel(None)
@@ -81,29 +102,97 @@ class ChatWebOutput(QWebEngineView):
81
102
  # bridge, channel, and signals have parent=page, so deleteLater of the page will clean them up
82
103
  page.deleteLater()
83
104
 
105
+ def _create_profile(self, parent=None) -> QWebEngineProfile:
106
+ """
107
+ Create a new QWebEngineProfile with off-the-record settings
108
+
109
+ :param parent: QWidget - parent widget
110
+ :return: QWebEngineProfile - new profile instance
111
+ """
112
+ p = QWebEngineProfile(parent or self)
113
+ try:
114
+ p.setHttpCacheType(QWebEngineProfile.NoCache)
115
+ p.setHttpCacheMaximumSize(0)
116
+ p.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
117
+ except Exception:
118
+ pass
119
+ return p
120
+
121
+ def hard_reset(self, focus_after=True):
122
+ """
123
+ Hard reset of the view, creating a new instance of ChatWebOutput
124
+
125
+ :param focus_after: bool - whether to focus the new view after reset
126
+ """
127
+ parent = self.parentWidget()
128
+ if parent is None or parent.layout() is None:
129
+ new_view = ChatWebOutput(self.window)
130
+ new_view.set_tab(self.tab)
131
+ new_view.set_meta(self.meta)
132
+ QTimer.singleShot(0, self.on_delete)
133
+ return new_view
134
+
135
+ layout = parent.layout()
136
+ idx = layout.indexOf(self)
137
+ if idx < 0:
138
+ idx = layout.count()
139
+
140
+ new_view = ChatWebOutput(self.window)
141
+ new_view.setProperty('class', self.property('class') or 'layout-output-web')
142
+ new_view.set_tab(self.tab)
143
+ new_view.set_meta(self.meta)
144
+
145
+ try:
146
+ z = self.get_zoom_value()
147
+ p = new_view.page()
148
+ if p:
149
+ p.setZoomFactor(z)
150
+ except Exception:
151
+ pass
152
+
153
+ layout.insertWidget(idx, new_view)
154
+ layout.removeWidget(self)
155
+ self.hide()
156
+
157
+ QTimer.singleShot(0, self.on_delete)
158
+ if focus_after:
159
+ try:
160
+ new_view.setFocus()
161
+ except Exception:
162
+ pass
163
+ try:
164
+ mem_clean()
165
+ except Exception:
166
+ pass
167
+
168
+ return new_view
169
+
84
170
  def resetPage(self):
85
171
  """Reset current page (clear memory)"""
86
172
  self.meta = None
87
173
  self.plain = ""
88
174
  self.html_content = ""
89
175
 
90
- try:
91
- (self.page().profile() if self.page() else QWebEngineProfile.defaultProfile()).clearHttpCache()
92
- except Exception:
93
- pass
94
-
95
- self.setUpdatesEnabled(False)
96
176
  old_page = self.page()
97
- prof = old_page.profile() if old_page else QWebEngineProfile.defaultProfile()
98
- new_page = CustomWebEnginePage(self.window, self, profile=prof)
177
+ old_profile = getattr(self, "_profile", None)
99
178
 
179
+ self.setUpdatesEnabled(False)
180
+ new_profile = self._create_profile(parent=self)
181
+ new_page = CustomWebEnginePage(self.window, self, profile=new_profile)
100
182
  self.setPage(new_page)
101
- self._teardown_page(old_page)
102
183
 
184
+ if old_page:
185
+ self._teardown_page(old_page)
186
+
187
+ self._release_profile_after_page(old_page, old_profile)
188
+ self._profile = new_profile
189
+
190
+ QTimer.singleShot(0, lambda: QApplication.sendPostedEvents(None, QEvent.DeferredDelete))
103
191
  mem_clean()
104
192
 
105
193
  def on_delete(self):
106
194
  """Clean up on delete"""
195
+ self._detach_gl_event_filter()
107
196
  if self.finder:
108
197
  try:
109
198
  self.finder.disconnect()
@@ -114,11 +203,14 @@ class ChatWebOutput(QWebEngineView):
114
203
  self.tab = None
115
204
  self.meta = None
116
205
 
117
- # remove the page along with the channel and Bridge
206
+ # remove the page and profile
118
207
  page = self.page()
208
+ prof = getattr(self, "_profile", None)
119
209
  if page:
120
210
  self._teardown_page(page)
121
211
 
212
+ self._release_profile_after_page(page, prof)
213
+
122
214
  # safely unhook signals (may not have been hooked)
123
215
  for sig, slot in (
124
216
  (self.loadFinished, self.on_page_loaded),
@@ -136,25 +228,35 @@ class ChatWebOutput(QWebEngineView):
136
228
 
137
229
  def eventFilter(self, source, event):
138
230
  """
139
- Focus event filter
231
+ Event filter to handle child added events and mouse button presses
140
232
 
141
- :param source: source
142
- :param event: event
233
+ :param source: QWidget - source of the event
234
+ :param event: QEvent - event to filter
143
235
  """
144
- if (event.type() == QEvent.ChildAdded and
145
- source is self and
146
- event.child().isWidgetType()):
236
+ if event.type() == QEvent.ChildAdded and source is self and event.child().isWidgetType():
237
+ self._detach_gl_event_filter()
147
238
  self._glwidget = event.child()
148
- self._glwidget.installEventFilter(self)
149
- elif (event.type() == event.Type.MouseButtonPress):
150
- col_idx = self.tab.column_idx
151
- self.window.controller.ui.tabs.on_column_focus(col_idx)
239
+ try:
240
+ self._glwidget.installEventFilter(self)
241
+ self._glwidget_filter_installed = True
242
+ except Exception:
243
+ self._glwidget = None
244
+ self._glwidget_filter_installed = False
245
+
246
+ elif event.type() == QEvent.Type.MouseButtonPress:
247
+ try:
248
+ col_idx = self.tab.column_idx
249
+ self.window.controller.ui.tabs.on_column_focus(col_idx)
250
+ except Exception:
251
+ pass
152
252
 
153
253
  return super().eventFilter(source, event)
154
254
 
155
255
  def on_focus(self, widget):
156
256
  """
157
257
  On widget clicked
258
+
259
+ :param widget: QWidget - widget that received focus
158
260
  """
159
261
  if self.tab is not None:
160
262
  self.window.controller.ui.tabs.on_column_focus(self.tab.column_idx)
@@ -405,6 +507,13 @@ class Bridge(QObject):
405
507
  super(Bridge, self).__init__(parent)
406
508
  self.window = window
407
509
 
510
+ chunk = Signal(str, str, str, bool, bool) # name, buffer, chunk, replace, is_code
511
+ readyChanged = Signal(bool)
512
+
513
+ @Slot()
514
+ def js_ready(self):
515
+ self.readyChanged.emit(True)
516
+
408
517
  @Slot(str)
409
518
  def log(self, text: str):
410
519
  print(f"JS log: {text}")
pygpt_net/utils.py CHANGED
@@ -6,13 +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.11 00:00:00 #
9
+ # Updated Date: 2025.08.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
13
13
  import os
14
14
  import re
15
15
  from datetime import datetime
16
+
17
+ from PySide6.QtCore import QEvent, QCoreApplication
18
+
16
19
  from pygpt_net.core.locale import Locale
17
20
 
18
21
  locale = None
@@ -265,6 +268,10 @@ def mem_clean():
265
268
  gc.collect()
266
269
  except Exception:
267
270
  pass
271
+ try:
272
+ QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
273
+ except Exception:
274
+ pass
268
275
  try:
269
276
  if sys.platform.startswith("linux"):
270
277
  import ctypes, ctypes.util
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.5
3
+ Version: 2.6.7
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.5** | build: **2025-08-16** | Python: **>=3.10, <3.14**
111
+ Release: **2.6.7** | build: **2025-08-16** | Python: **>=3.10, <3.14**
112
112
 
113
113
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
114
114
  >
@@ -4514,6 +4514,14 @@ may consume additional tokens that are not displayed in the main window.
4514
4514
 
4515
4515
  ## Recent changes:
4516
4516
 
4517
+ **2.6.7 (2025-08-16)**
4518
+
4519
+ - Fix: missing entity sanitize.
4520
+
4521
+ **2.6.6 (2025-08-16)**
4522
+
4523
+ - Output rendering optimization.
4524
+
4517
4525
  **2.6.5 (2025-08-16)**
4518
4526
 
4519
4527
  - Fix: crash when creating a context in a new group.
@@ -1,7 +1,7 @@
1
- pygpt_net/CHANGELOG.txt,sha256=mvVtaw-AxYs5ifXHy-Gs_r7ukns1ze0M0iciOFN3HZI,98671
1
+ pygpt_net/CHANGELOG.txt,sha256=QZ2QQvtQrUcsSAcitx_akp0H01DcWJ2jZYbidxX4gPo,98778
2
2
  pygpt_net/LICENSE,sha256=dz9sfFgYahvu2NZbx4C1xCsVn9GVer2wXcMkFRBvqzY,1146
3
- pygpt_net/__init__.py,sha256=U14uzsWSvgzovsz6FgJpZ6vOsDYPVY6FKPbZp8bZJj8,1372
4
- pygpt_net/app.py,sha256=x4nLXzo2dDv4mnWFVfJ-t-SPBmZ-vMa-NwvZTjMAHMw,20449
3
+ pygpt_net/__init__.py,sha256=vTKB9aiZrQ0r8CZYy5EkKL41A0JgiVn4Eg-_IjSm6gk,1372
4
+ pygpt_net/app.py,sha256=H0bDAM1kNQswEPnMRgbxJ_wa5KF1LIG-eLwAhXzdNxI,20675
5
5
  pygpt_net/config.py,sha256=P-D-vRWwq77moEMveMNyUImH8hG3PXpxqBsWyriEGzs,16731
6
6
  pygpt_net/container.py,sha256=NsMSHURaEC_eW8vrCNdztwqkxB7jui3yVlzUOMYvCHg,4124
7
7
  pygpt_net/controller/__init__.py,sha256=FiAP-Md0a57HSH1sSFflB4aq6Jho9M1lejk9VJxM8is,5969
@@ -43,7 +43,7 @@ pygpt_net/controller/chat/input.py,sha256=YJ_e-oNVcJewrRJznDXLmpAdoTdb9PkXdHy0dG
43
43
  pygpt_net/controller/chat/output.py,sha256=lqb7gCTTVfTocXJdjWvLhCOQrfR6wph2NGqpuILhHTo,10877
44
44
  pygpt_net/controller/chat/render.py,sha256=Vxy2523puEL0wk9tbvgF24AX7NWCTS2rNTTKHF89fVc,20191
45
45
  pygpt_net/controller/chat/response.py,sha256=vJqS6KkxXubqq07fTEgVXrZxInIfF96gBlCA9SBGc9s,12417
46
- pygpt_net/controller/chat/stream.py,sha256=RsnFPnsDYUm9doycrM2qfbYRvW_Mm3PfVL6nrWcOzPo,21807
46
+ pygpt_net/controller/chat/stream.py,sha256=5k70B4izs8cdv4dhzr4P13VWXTE91eJv6NapRUN_5QA,21817
47
47
  pygpt_net/controller/chat/text.py,sha256=6tR6c733GN0qKX1s27_xO2j47Qnj8Rdl7kIPaFHcIm4,10659
48
48
  pygpt_net/controller/chat/vision.py,sha256=LsFc0TZZwY8dVtJH6Q5iha8rUQCf5HhOMuRXMtnLzZU,3578
49
49
  pygpt_net/controller/command/__init__.py,sha256=xHOjVYXKiHT-FEXNSGJZoYcU8SslQ2vr6DMqJcVPDNE,511
@@ -303,11 +303,11 @@ pygpt_net/core/render/plain/helpers.py,sha256=CMF84kSeuQnkgZVHmN_9YWaL5BC958tDE9
303
303
  pygpt_net/core/render/plain/pid.py,sha256=Pz3v1tnLj-XI_9vcaVkCf9SZ2EgVs4LYV4qzelBMoOg,1119
304
304
  pygpt_net/core/render/plain/renderer.py,sha256=Cm-HXHd5Mc6LAiDW5qNXOyZoLKlUo16I_cU-B2cGnYM,15595
305
305
  pygpt_net/core/render/web/__init__.py,sha256=istp5dsn6EkLEP7lOBeDb8RjodUcWZqjcEvTroaTT-w,489
306
- pygpt_net/core/render/web/body.py,sha256=-a5T4ufjzyUhGPw_VhrjxxGxaGA_bFMEVwe9L_YNw-w,55236
306
+ pygpt_net/core/render/web/body.py,sha256=3BqHyBibKQOo_3ikLOvytTJGm6OG519A2i9eRUhmzfo,55975
307
307
  pygpt_net/core/render/web/helpers.py,sha256=ivrXrCqRIUWHDmu3INu-i6XUlB2W9IOO8iYyqpbnSRU,5438
308
308
  pygpt_net/core/render/web/parser.py,sha256=2ATdydXNDIgf4Jq44m1kvd7Vr4mW_FopovrYbwfv7rc,12740
309
309
  pygpt_net/core/render/web/pid.py,sha256=EOp8hUJMFXNgZs8Kl5Qt8fz3Rslj6meK8Yn9aDmUrdM,3092
310
- pygpt_net/core/render/web/renderer.py,sha256=xMTG8vw9l96VUHnY2OUd5CqcX4b-HzOQ6oLhCc_0vqg,48213
310
+ pygpt_net/core/render/web/renderer.py,sha256=SCeUFC6GxAAvmxg07N1EM6FALaBwEGtLLMd05i3T2P8,47301
311
311
  pygpt_net/core/render/web/syntax_highlight.py,sha256=QSLGF5cJL_Xeqej7_TYwY_5C2w9enXV_cMEuaJ3C43U,2005
312
312
  pygpt_net/core/settings/__init__.py,sha256=GQ6_gJ2jf_Chm7ZuZLvkcvEh_sfMDVMBieeoJi2iPI4,512
313
313
  pygpt_net/core/settings/settings.py,sha256=onqwNiICm2VhHfmXLvp1MiEJ14m2jzeeI2pjUiaUwtY,7787
@@ -343,8 +343,8 @@ pygpt_net/css_rc.py,sha256=i13kX7irhbYCWZ5yJbcMmnkFp_UfS4PYnvRFSPF7XXo,11349
343
343
  pygpt_net/data/audio/click_off.mp3,sha256=aNiRDP1pt-Jy7ija4YKCNFBwvGWbzU460F4pZWZDS90,65201
344
344
  pygpt_net/data/audio/click_on.mp3,sha256=qfdsSnthAEHVXzeyN4LlC0OvXuyW8p7stb7VXtlvZ1k,65201
345
345
  pygpt_net/data/audio/ok.mp3,sha256=LTiV32pEBkpUGBkKkcOdOFB7Eyt_QoP2Nv6c5AaXftk,32256
346
- pygpt_net/data/config/config.json,sha256=6n3FowUr_93yKePLtAhp9HmQ9oU4AXTqXAHKujecXRI,24887
347
- pygpt_net/data/config/models.json,sha256=ZQsOYPZ6X3pOwCoV6dkRLFiZJ9oCskmD4V1_6cKe6MI,109648
346
+ pygpt_net/data/config/config.json,sha256=zdQC0oIjws1UtweU7xqa3T3sqx2Ogpt6vmqwRqTayjs,24887
347
+ pygpt_net/data/config/models.json,sha256=qNfg4fj0JoOvAKufESkCwzc236M7FbwwnG1u-HVK0qE,109648
348
348
  pygpt_net/data/config/modes.json,sha256=M882iiqX_R2sNQl9cqZ3k-uneEvO9wpARtHRMLx_LHw,2265
349
349
  pygpt_net/data/config/presets/agent_code_act.json,sha256=GYHqhxtKFLUCvRI3IJAJ7Qe1k8yD9wGGNwManldWzlI,754
350
350
  pygpt_net/data/config/presets/agent_openai.json,sha256=vMTR-soRBiEZrpJJHuFLWyx8a3Ez_BqtqjyXgxCAM_Q,733
@@ -2307,7 +2307,7 @@ pygpt_net/ui/layout/toolbox/presets.py,sha256=uEmMy3gudpjKFQbDVNShjK1iBw-bl-1IXS
2307
2307
  pygpt_net/ui/layout/toolbox/prompt.py,sha256=jebF-q1S1Et6ISa9vI0_nM4sb7liDesAXJHtZ5Ll7ZI,4006
2308
2308
  pygpt_net/ui/layout/toolbox/toolbox.py,sha256=zEZr_XDz9QbPKL0u0KMSt1b8yOG-ao1gmZPvWWVpuVs,3392
2309
2309
  pygpt_net/ui/layout/toolbox/vision.py,sha256=GZY-N2z8re1LN1ntsy-3Ius8OY4DujmJpyJ1qP2ZRxs,2447
2310
- pygpt_net/ui/main.py,sha256=CbymWU_TXLgwssgaE0lsnNqRLgoVPmNp_rDtkcohaNM,13391
2310
+ pygpt_net/ui/main.py,sha256=ObCj3nMmjzDEWgNSdh0Dwn8BocEjmuSl-AALDQr2g8I,13783
2311
2311
  pygpt_net/ui/menu/__init__.py,sha256=wAIKG9wLWfYv6tpXCTXptWb_XKoCc-4lYWLDvV1bVYk,508
2312
2312
  pygpt_net/ui/menu/about.py,sha256=bs0iGge52THU72oyu98QBxvh5iKA9APQIStRKQT5IQc,5891
2313
2313
  pygpt_net/ui/menu/audio.py,sha256=Sb8NTAyMnPj4johTvBKwocHzq67XypIdw7K7hjf2760,3494
@@ -2429,12 +2429,12 @@ pygpt_net/ui/widget/textarea/output.py,sha256=8T2spzqVYHKopSB83p1ULazGZ14nFJhXLB
2429
2429
  pygpt_net/ui/widget/textarea/rename.py,sha256=NwuGRIeWMo7WfsMguAFpTqdOz1eTiXbxrDXGsbWF_TY,1358
2430
2430
  pygpt_net/ui/widget/textarea/search_input.py,sha256=phEXf50VcfCRBen0p2iEAzuX2zmrSE3nWVRfWmtHKpo,5228
2431
2431
  pygpt_net/ui/widget/textarea/url.py,sha256=xbNQxoM5fYI1ZWbvybQkPmNPrIq3yhtNPBOSOWftZCg,1337
2432
- pygpt_net/ui/widget/textarea/web.py,sha256=G0_mOsZj8G5X7tVT-bSF0WrgqyeDIivplu1lYRiCJrM,14305
2432
+ pygpt_net/ui/widget/textarea/web.py,sha256=6Kt9IUkjx3rhWBmH3xdnmX1X-ACUMYnfHnRN0qoPw2g,17798
2433
2433
  pygpt_net/ui/widget/vision/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
2434
2434
  pygpt_net/ui/widget/vision/camera.py,sha256=T8b5cmK6uhf_WSSxzPt_Qod8JgMnst6q8sQqRvgQiSA,2584
2435
- pygpt_net/utils.py,sha256=uQ8UbvXOK1rZDFskfCiLKBtI8Cd0UEDFqVYQvrM9kcQ,8666
2436
- pygpt_net-2.6.5.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
2437
- pygpt_net-2.6.5.dist-info/METADATA,sha256=ZLYyvcV8P7QathDkzrMgR9XxxKXaVKpY-cpOgs7Haw8,186517
2438
- pygpt_net-2.6.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
2439
- pygpt_net-2.6.5.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
2440
- pygpt_net-2.6.5.dist-info/RECORD,,
2435
+ pygpt_net/utils.py,sha256=YL0czRa1v6ilKNszAI9NyaE9Lgz6HiUGpNFAYQ9Wj8s,8835
2436
+ pygpt_net-2.6.7.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
2437
+ pygpt_net-2.6.7.dist-info/METADATA,sha256=2phPEirUV7ohpBd1unAhVh7eQ9Uoy6LUQLcLuiYmdEM,186632
2438
+ pygpt_net-2.6.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
2439
+ pygpt_net-2.6.7.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
2440
+ pygpt_net-2.6.7.dist-info/RECORD,,