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.
- pygpt_net/CHANGELOG.txt +10 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +7 -1
- pygpt_net/config.py +11 -11
- pygpt_net/controller/access/access.py +49 -2
- pygpt_net/controller/chat/attachment.py +13 -13
- pygpt_net/controller/chat/command.py +4 -4
- pygpt_net/controller/chat/common.py +9 -14
- pygpt_net/controller/chat/files.py +2 -2
- pygpt_net/controller/chat/input.py +4 -4
- pygpt_net/controller/chat/output.py +4 -4
- pygpt_net/controller/chat/render.py +11 -6
- pygpt_net/controller/chat/response.py +7 -7
- pygpt_net/controller/chat/stream.py +11 -6
- pygpt_net/controller/chat/text.py +15 -10
- pygpt_net/controller/command/command.py +7 -7
- pygpt_net/controller/ctx/ctx.py +9 -5
- pygpt_net/controller/debug/debug.py +2 -2
- pygpt_net/core/ctx/bag.py +2 -1
- pygpt_net/core/debug/debug.py +17 -3
- pygpt_net/core/dispatcher/dispatcher.py +5 -3
- pygpt_net/core/events/render.py +3 -0
- pygpt_net/core/render/base.py +4 -4
- pygpt_net/core/render/web/body.py +83 -88
- pygpt_net/core/render/web/parser.py +11 -6
- pygpt_net/core/render/web/pid.py +19 -4
- pygpt_net/core/render/web/renderer.py +217 -74
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/presets/agent_openai.json +1 -1
- pygpt_net/data/config/presets/agent_openai_assistant.json +1 -1
- pygpt_net/data/config/presets/agent_planner.json +1 -1
- pygpt_net/data/config/presets/agent_react.json +1 -1
- pygpt_net/item/ctx.py +3 -3
- pygpt_net/launcher.py +2 -9
- pygpt_net/provider/gpt/__init__.py +13 -4
- pygpt_net/tools/code_interpreter/body.py +2 -3
- pygpt_net/ui/main.py +8 -3
- pygpt_net/ui/widget/textarea/html.py +2 -7
- pygpt_net/ui/widget/textarea/web.py +52 -28
- pygpt_net/utils.py +15 -8
- {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/METADATA +12 -2
- {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/RECORD +46 -46
- {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.10.dist-info → pygpt_net-2.6.12.dist-info}/entry_points.txt +0 -0
|
@@ -6,14 +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.
|
|
9
|
+
# Updated Date: 2025.08.19 07:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
|
+
|
|
15
16
|
from datetime import datetime
|
|
16
17
|
from typing import Optional, List, Any
|
|
18
|
+
from time import monotonic
|
|
17
19
|
|
|
18
20
|
from pygpt_net.core.render.base import BaseRenderer
|
|
19
21
|
from pygpt_net.core.text.utils import has_unclosed_code_tag
|
|
@@ -31,6 +33,7 @@ from pygpt_net.core.events import RenderEvent
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class Renderer(BaseRenderer):
|
|
36
|
+
|
|
34
37
|
NODE_INPUT = 0
|
|
35
38
|
NODE_OUTPUT = 1
|
|
36
39
|
ENDINGS_CODE = (
|
|
@@ -43,6 +46,7 @@ class Renderer(BaseRenderer):
|
|
|
43
46
|
"</ol>",
|
|
44
47
|
"</li>"
|
|
45
48
|
)
|
|
49
|
+
RE_AMP_LT_GT = re.compile(r'&(lt|gt);')
|
|
46
50
|
|
|
47
51
|
def __init__(self, window=None):
|
|
48
52
|
super(Renderer, self).__init__(window)
|
|
@@ -55,7 +59,7 @@ class Renderer(BaseRenderer):
|
|
|
55
59
|
self.body = Body(window)
|
|
56
60
|
self.helpers = Helpers(window)
|
|
57
61
|
self.parser = Parser(window)
|
|
58
|
-
self.pids = {}
|
|
62
|
+
self.pids = {}
|
|
59
63
|
self.prev_chunk_replace = False
|
|
60
64
|
self.prev_chunk_newline = False
|
|
61
65
|
|
|
@@ -64,6 +68,9 @@ class Renderer(BaseRenderer):
|
|
|
64
68
|
self._icon_sync = os.path.join(app_path, "data", "icons", "sync.svg")
|
|
65
69
|
self._file_prefix = 'file:///' if self.window and self.window.core.platforms.is_windows() else 'file://'
|
|
66
70
|
|
|
71
|
+
self._thr = {}
|
|
72
|
+
self._throttle_interval = 0.01 # 10 ms delay
|
|
73
|
+
|
|
67
74
|
def prepare(self):
|
|
68
75
|
"""
|
|
69
76
|
Prepare renderer
|
|
@@ -82,7 +89,7 @@ class Renderer(BaseRenderer):
|
|
|
82
89
|
self.parser.reset()
|
|
83
90
|
try:
|
|
84
91
|
node.page().runJavaScript("if (typeof window.prepare !== 'undefined') prepare();")
|
|
85
|
-
except Exception
|
|
92
|
+
except Exception:
|
|
86
93
|
pass
|
|
87
94
|
|
|
88
95
|
def on_page_loaded(
|
|
@@ -102,8 +109,6 @@ class Renderer(BaseRenderer):
|
|
|
102
109
|
if pid is None or pid not in self.pids:
|
|
103
110
|
return
|
|
104
111
|
self.pids[pid].loaded = True
|
|
105
|
-
node = self.get_output_node(meta)
|
|
106
|
-
|
|
107
112
|
if self.pids[pid].html != "" and not self.pids[pid].use_buffer:
|
|
108
113
|
self.clear_chunks_input(pid)
|
|
109
114
|
self.clear_chunks_output(pid)
|
|
@@ -111,8 +116,6 @@ class Renderer(BaseRenderer):
|
|
|
111
116
|
self.append(pid, self.pids[pid].html, flush=True)
|
|
112
117
|
self.pids[pid].html = ""
|
|
113
118
|
|
|
114
|
-
node.setUpdatesEnabled(True)
|
|
115
|
-
|
|
116
119
|
def get_pid(self, meta: CtxMeta):
|
|
117
120
|
"""
|
|
118
121
|
Get PID for context meta
|
|
@@ -279,7 +282,7 @@ class Renderer(BaseRenderer):
|
|
|
279
282
|
self.prev_chunk_replace = False
|
|
280
283
|
try:
|
|
281
284
|
self.get_output_node(meta).page().runJavaScript("beginStream();")
|
|
282
|
-
except Exception
|
|
285
|
+
except Exception:
|
|
283
286
|
pass
|
|
284
287
|
|
|
285
288
|
def stream_end(
|
|
@@ -297,6 +300,9 @@ class Renderer(BaseRenderer):
|
|
|
297
300
|
pid = self.get_or_create_pid(meta)
|
|
298
301
|
if pid is None:
|
|
299
302
|
return
|
|
303
|
+
|
|
304
|
+
self._throttle_emit(pid, force=True)
|
|
305
|
+
self._throttle_reset(pid)
|
|
300
306
|
if self.window.controller.agent.legacy.enabled():
|
|
301
307
|
if self.pids[pid].item is not None:
|
|
302
308
|
self.append_context_item(meta, self.pids[pid].item)
|
|
@@ -304,7 +310,7 @@ class Renderer(BaseRenderer):
|
|
|
304
310
|
self.pids[pid].clear()
|
|
305
311
|
try:
|
|
306
312
|
self.get_output_node(meta).page().runJavaScript("endStream();")
|
|
307
|
-
except Exception
|
|
313
|
+
except Exception:
|
|
308
314
|
pass
|
|
309
315
|
|
|
310
316
|
def append_context(
|
|
@@ -333,6 +339,7 @@ class Renderer(BaseRenderer):
|
|
|
333
339
|
self.pids[pid].use_buffer = True
|
|
334
340
|
self.pids[pid].html = ""
|
|
335
341
|
prev_ctx = None
|
|
342
|
+
next_item = None
|
|
336
343
|
total = len(items)
|
|
337
344
|
for i, item in enumerate(items):
|
|
338
345
|
self.update_names(meta, item)
|
|
@@ -347,14 +354,17 @@ class Renderer(BaseRenderer):
|
|
|
347
354
|
next_ctx=next_item
|
|
348
355
|
)
|
|
349
356
|
prev_ctx = item
|
|
350
|
-
self.pids[pid].use_buffer = False
|
|
351
357
|
|
|
358
|
+
prev_ctx = None
|
|
359
|
+
next_item = None
|
|
360
|
+
self.pids[pid].use_buffer = False
|
|
352
361
|
if self.pids[pid].html != "":
|
|
353
362
|
self.append(
|
|
354
363
|
pid,
|
|
355
364
|
self.pids[pid].html,
|
|
356
365
|
flush=True
|
|
357
366
|
)
|
|
367
|
+
self.parser.reset()
|
|
358
368
|
|
|
359
369
|
def append_input(
|
|
360
370
|
self, meta: CtxMeta,
|
|
@@ -465,6 +475,8 @@ class Renderer(BaseRenderer):
|
|
|
465
475
|
if not text_chunk:
|
|
466
476
|
if begin:
|
|
467
477
|
pctx.clear()
|
|
478
|
+
self._throttle_emit(pid, force=True)
|
|
479
|
+
self._throttle_reset(pid)
|
|
468
480
|
return
|
|
469
481
|
|
|
470
482
|
name_header_str = self.get_name_header(ctx)
|
|
@@ -477,8 +489,10 @@ class Renderer(BaseRenderer):
|
|
|
477
489
|
debug = self.append_debug(ctx, pid, "stream")
|
|
478
490
|
if debug:
|
|
479
491
|
text_chunk = debug + text_chunk
|
|
480
|
-
|
|
481
|
-
|
|
492
|
+
self._throttle_emit(pid, force=True)
|
|
493
|
+
self._throttle_reset(pid)
|
|
494
|
+
pctx.clear()
|
|
495
|
+
pctx.is_cmd = False
|
|
482
496
|
self.clear_chunks_output(pid)
|
|
483
497
|
self.prev_chunk_replace = False
|
|
484
498
|
|
|
@@ -491,53 +505,43 @@ class Renderer(BaseRenderer):
|
|
|
491
505
|
buffer_to_parse = buffer
|
|
492
506
|
|
|
493
507
|
html = self.parser.parse(buffer_to_parse)
|
|
508
|
+
del buffer_to_parse
|
|
494
509
|
is_code_block = html.endswith(self.ENDINGS_CODE)
|
|
495
510
|
is_list = html.endswith(self.ENDINGS_LIST)
|
|
496
|
-
|
|
511
|
+
is_n = "\n" in text_chunk
|
|
512
|
+
is_newline = is_n or buffer.endswith("\n") or is_code_block
|
|
497
513
|
force_replace = False
|
|
498
514
|
if self.prev_chunk_newline:
|
|
499
515
|
force_replace = True
|
|
500
|
-
if
|
|
516
|
+
if is_n:
|
|
501
517
|
self.prev_chunk_newline = True
|
|
502
518
|
else:
|
|
503
519
|
self.prev_chunk_newline = False
|
|
504
520
|
|
|
505
|
-
|
|
521
|
+
replace = False
|
|
506
522
|
if is_newline or force_replace or is_list:
|
|
507
|
-
|
|
523
|
+
replace = True
|
|
508
524
|
if is_code_block:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
# if there is no newline in raw_chunk, then don't replace
|
|
512
|
-
replace_bool = False
|
|
525
|
+
if not is_n:
|
|
526
|
+
replace = False
|
|
513
527
|
|
|
514
528
|
if not is_code_block:
|
|
515
|
-
|
|
529
|
+
if is_n:
|
|
530
|
+
text_chunk = text_chunk.replace("\n", "<br/>")
|
|
516
531
|
else:
|
|
517
|
-
if self.prev_chunk_replace and not has_unclosed_code_tag(text_chunk):
|
|
518
|
-
|
|
519
|
-
text_chunk = "".join(("\n", text_chunk)) # add newline to chunk
|
|
532
|
+
if self.prev_chunk_replace and (is_code_block and not has_unclosed_code_tag(text_chunk)):
|
|
533
|
+
text_chunk = "\n" + text_chunk
|
|
520
534
|
|
|
521
|
-
self.prev_chunk_replace =
|
|
535
|
+
self.prev_chunk_replace = replace
|
|
522
536
|
|
|
523
|
-
# hide loading spinner if it is the beginning of the text
|
|
524
537
|
if begin:
|
|
525
538
|
try:
|
|
526
539
|
self.get_output_node(meta).page().runJavaScript("hideLoading();")
|
|
527
|
-
except Exception
|
|
540
|
+
except Exception:
|
|
528
541
|
pass
|
|
529
542
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
self.get_output_node(meta).page().bridge.chunk.emit(
|
|
533
|
-
name_header_str or "",
|
|
534
|
-
html if replace_bool else "",
|
|
535
|
-
text_chunk if not replace_bool else "",
|
|
536
|
-
bool(replace_bool),
|
|
537
|
-
bool(is_code_block),
|
|
538
|
-
)
|
|
539
|
-
except Exception as e:
|
|
540
|
-
pass
|
|
543
|
+
self._throttle_queue(pid, name_header_str or "", html, text_chunk, replace, bool(is_code_block))
|
|
544
|
+
self._throttle_emit(pid, force=False)
|
|
541
545
|
|
|
542
546
|
def next_chunk(
|
|
543
547
|
self,
|
|
@@ -551,6 +555,8 @@ class Renderer(BaseRenderer):
|
|
|
551
555
|
:param ctx: context item
|
|
552
556
|
"""
|
|
553
557
|
pid = self.get_or_create_pid(meta)
|
|
558
|
+
self._throttle_emit(pid, force=True)
|
|
559
|
+
self._throttle_reset(pid)
|
|
554
560
|
self.pids[pid].item = ctx
|
|
555
561
|
self.pids[pid].buffer = ""
|
|
556
562
|
self.update_names(meta, ctx)
|
|
@@ -558,8 +564,9 @@ class Renderer(BaseRenderer):
|
|
|
558
564
|
self.prev_chunk_newline = False
|
|
559
565
|
try:
|
|
560
566
|
self.get_output_node(meta).page().runJavaScript(
|
|
561
|
-
"nextStream();"
|
|
562
|
-
|
|
567
|
+
"nextStream();"
|
|
568
|
+
)
|
|
569
|
+
except Exception:
|
|
563
570
|
pass
|
|
564
571
|
|
|
565
572
|
def append_chunk_input(
|
|
@@ -586,9 +593,13 @@ class Renderer(BaseRenderer):
|
|
|
586
593
|
self.clear_chunks_input(pid)
|
|
587
594
|
try:
|
|
588
595
|
self.get_output_node(meta).page().runJavaScript(
|
|
589
|
-
f"appendToInput({self.to_json(
|
|
596
|
+
f"""appendToInput({self.to_json(
|
|
597
|
+
self.sanitize_html(
|
|
598
|
+
self.helpers.format_chunk(text_chunk)
|
|
599
|
+
)
|
|
600
|
+
)});"""
|
|
590
601
|
)
|
|
591
|
-
except Exception
|
|
602
|
+
except Exception:
|
|
592
603
|
pass
|
|
593
604
|
|
|
594
605
|
def append_live(
|
|
@@ -630,9 +641,14 @@ class Renderer(BaseRenderer):
|
|
|
630
641
|
to_append += "\n```"
|
|
631
642
|
try:
|
|
632
643
|
self.get_output_node(meta).page().runJavaScript(
|
|
633
|
-
f"replaceLive({self.to_json(
|
|
644
|
+
f"""replaceLive({self.to_json(
|
|
645
|
+
self.sanitize_html(
|
|
646
|
+
self.parser.parse(to_append)
|
|
647
|
+
)
|
|
648
|
+
)});"""
|
|
634
649
|
)
|
|
635
650
|
except Exception as e:
|
|
651
|
+
print(e)
|
|
636
652
|
pass
|
|
637
653
|
|
|
638
654
|
def clear_live(self, meta: CtxMeta, ctx: CtxItem):
|
|
@@ -651,7 +667,7 @@ class Renderer(BaseRenderer):
|
|
|
651
667
|
js = "clearLive();"
|
|
652
668
|
try:
|
|
653
669
|
self.get_output_node_by_pid(pid).page().runJavaScript(js)
|
|
654
|
-
except Exception
|
|
670
|
+
except Exception:
|
|
655
671
|
pass
|
|
656
672
|
|
|
657
673
|
def append_node(
|
|
@@ -704,8 +720,9 @@ class Renderer(BaseRenderer):
|
|
|
704
720
|
"""
|
|
705
721
|
if self.pids[pid].loaded and not self.pids[pid].use_buffer:
|
|
706
722
|
self.clear_chunks(pid)
|
|
707
|
-
|
|
708
|
-
|
|
723
|
+
if html:
|
|
724
|
+
self.flush_output(pid, html)
|
|
725
|
+
self.pids[pid].clear()
|
|
709
726
|
else:
|
|
710
727
|
if not flush:
|
|
711
728
|
self.pids[pid].append_html(html)
|
|
@@ -824,7 +841,11 @@ class Renderer(BaseRenderer):
|
|
|
824
841
|
self.append(pid, html)
|
|
825
842
|
else:
|
|
826
843
|
try:
|
|
827
|
-
self.get_output_node(meta).page().runJavaScript(
|
|
844
|
+
self.get_output_node(meta).page().runJavaScript(
|
|
845
|
+
f"""appendExtra('{ctx.id}',{self.to_json(
|
|
846
|
+
self.sanitize_html(html)
|
|
847
|
+
)});"""
|
|
848
|
+
)
|
|
828
849
|
except Exception as e:
|
|
829
850
|
pass
|
|
830
851
|
|
|
@@ -894,6 +915,7 @@ class Renderer(BaseRenderer):
|
|
|
894
915
|
node.reset_current_content()
|
|
895
916
|
self.reset_names_by_pid(pid)
|
|
896
917
|
self.prev_chunk_replace = False
|
|
918
|
+
self._throttle_reset(pid)
|
|
897
919
|
|
|
898
920
|
def clear_input(self):
|
|
899
921
|
"""Clear input"""
|
|
@@ -939,7 +961,7 @@ class Renderer(BaseRenderer):
|
|
|
939
961
|
js = "clearInput();"
|
|
940
962
|
try:
|
|
941
963
|
self.get_output_node_by_pid(pid).page().runJavaScript(js)
|
|
942
|
-
except Exception
|
|
964
|
+
except Exception:
|
|
943
965
|
pass
|
|
944
966
|
|
|
945
967
|
def clear_chunks_output(
|
|
@@ -958,8 +980,9 @@ class Renderer(BaseRenderer):
|
|
|
958
980
|
js = "clearOutput();"
|
|
959
981
|
try:
|
|
960
982
|
self.get_output_node_by_pid(pid).page().runJavaScript(js)
|
|
961
|
-
except Exception
|
|
983
|
+
except Exception:
|
|
962
984
|
pass
|
|
985
|
+
self._throttle_reset(pid)
|
|
963
986
|
|
|
964
987
|
def clear_nodes(
|
|
965
988
|
self,
|
|
@@ -976,7 +999,7 @@ class Renderer(BaseRenderer):
|
|
|
976
999
|
js = "clearNodes();"
|
|
977
1000
|
try:
|
|
978
1001
|
self.get_output_node_by_pid(pid).page().runJavaScript(js)
|
|
979
|
-
except Exception
|
|
1002
|
+
except Exception:
|
|
980
1003
|
pass
|
|
981
1004
|
|
|
982
1005
|
def prepare_node(
|
|
@@ -1174,10 +1197,11 @@ class Renderer(BaseRenderer):
|
|
|
1174
1197
|
"""
|
|
1175
1198
|
try:
|
|
1176
1199
|
self.get_output_node_by_pid(pid).page().runJavaScript(
|
|
1177
|
-
f"if (typeof window.appendNode !== 'undefined') appendNode({self.to_json(html)});"
|
|
1200
|
+
f"""if (typeof window.appendNode !== 'undefined') appendNode({self.to_json(self.sanitize_html(html))});"""
|
|
1178
1201
|
)
|
|
1179
|
-
except Exception
|
|
1202
|
+
except Exception:
|
|
1180
1203
|
pass
|
|
1204
|
+
html = None
|
|
1181
1205
|
|
|
1182
1206
|
def reload(self):
|
|
1183
1207
|
"""Reload output, called externally only on theme change to redraw content"""
|
|
@@ -1213,17 +1237,10 @@ class Renderer(BaseRenderer):
|
|
|
1213
1237
|
pid = self.get_or_create_pid(meta)
|
|
1214
1238
|
if pid is None:
|
|
1215
1239
|
return
|
|
1216
|
-
html = self.body.get_html(pid)
|
|
1217
|
-
self.pids[pid].loaded = False
|
|
1218
1240
|
node = self.get_output_node_by_pid(pid)
|
|
1219
1241
|
if node is not None:
|
|
1220
|
-
# hard reset
|
|
1221
|
-
# old_view = node
|
|
1222
|
-
# new_view = old_view.hard_reset()
|
|
1223
|
-
# self.window.ui.nodes['output'][pid] = new_view
|
|
1224
1242
|
node.resetPage()
|
|
1225
|
-
|
|
1226
|
-
self.pids[pid].html = ""
|
|
1243
|
+
self._throttle_reset(pid)
|
|
1227
1244
|
|
|
1228
1245
|
def get_output_node(
|
|
1229
1246
|
self,
|
|
@@ -1266,7 +1283,7 @@ class Renderer(BaseRenderer):
|
|
|
1266
1283
|
try:
|
|
1267
1284
|
self.get_output_node(ctx.meta).page().runJavaScript(
|
|
1268
1285
|
f"if (typeof window.removeNode !== 'undefined') removeNode({self.to_json(ctx.id)});")
|
|
1269
|
-
except Exception
|
|
1286
|
+
except Exception:
|
|
1270
1287
|
pass
|
|
1271
1288
|
|
|
1272
1289
|
def remove_items_from(self, ctx: CtxItem):
|
|
@@ -1278,7 +1295,7 @@ class Renderer(BaseRenderer):
|
|
|
1278
1295
|
try:
|
|
1279
1296
|
self.get_output_node(ctx.meta).page().runJavaScript(
|
|
1280
1297
|
f"if (typeof window.removeNodesFromId !== 'undefined') removeNodesFromId({self.to_json(ctx.id)});")
|
|
1281
|
-
except Exception
|
|
1298
|
+
except Exception:
|
|
1282
1299
|
pass
|
|
1283
1300
|
|
|
1284
1301
|
def reset_names(self, meta: CtxMeta):
|
|
@@ -1329,7 +1346,7 @@ class Renderer(BaseRenderer):
|
|
|
1329
1346
|
nodes = self.get_all_nodes()
|
|
1330
1347
|
for node in nodes:
|
|
1331
1348
|
node.page().runJavaScript("if (typeof window.enableEditIcons !== 'undefined') enableEditIcons();")
|
|
1332
|
-
except Exception
|
|
1349
|
+
except Exception:
|
|
1333
1350
|
pass
|
|
1334
1351
|
|
|
1335
1352
|
def on_disable_edit(self, live: bool = True):
|
|
@@ -1344,7 +1361,7 @@ class Renderer(BaseRenderer):
|
|
|
1344
1361
|
nodes = self.get_all_nodes()
|
|
1345
1362
|
for node in nodes:
|
|
1346
1363
|
node.page().runJavaScript("if (typeof window.disableEditIcons !== 'undefined') disableEditIcons();")
|
|
1347
|
-
except Exception
|
|
1364
|
+
except Exception:
|
|
1348
1365
|
pass
|
|
1349
1366
|
|
|
1350
1367
|
def on_enable_timestamp(self, live: bool = True):
|
|
@@ -1359,7 +1376,7 @@ class Renderer(BaseRenderer):
|
|
|
1359
1376
|
nodes = self.get_all_nodes()
|
|
1360
1377
|
for node in nodes:
|
|
1361
1378
|
node.page().runJavaScript("if (typeof window.enableTimestamp !== 'undefined') enableTimestamp();")
|
|
1362
|
-
except Exception
|
|
1379
|
+
except Exception:
|
|
1363
1380
|
pass
|
|
1364
1381
|
|
|
1365
1382
|
def on_disable_timestamp(self, live: bool = True):
|
|
@@ -1374,7 +1391,7 @@ class Renderer(BaseRenderer):
|
|
|
1374
1391
|
nodes = self.get_all_nodes()
|
|
1375
1392
|
for node in nodes:
|
|
1376
1393
|
node.page().runJavaScript("if (typeof window.disableTimestamp !== 'undefined') disableTimestamp();")
|
|
1377
|
-
except Exception
|
|
1394
|
+
except Exception:
|
|
1378
1395
|
pass
|
|
1379
1396
|
|
|
1380
1397
|
def update_names(
|
|
@@ -1402,6 +1419,7 @@ class Renderer(BaseRenderer):
|
|
|
1402
1419
|
self.clear_chunks(pid)
|
|
1403
1420
|
self.clear_nodes(pid)
|
|
1404
1421
|
self.pids[pid].html = ""
|
|
1422
|
+
self._throttle_reset(pid)
|
|
1405
1423
|
|
|
1406
1424
|
def scroll_to_bottom(self):
|
|
1407
1425
|
"""Scroll to bottom"""
|
|
@@ -1469,9 +1487,11 @@ class Renderer(BaseRenderer):
|
|
|
1469
1487
|
"""
|
|
1470
1488
|
try:
|
|
1471
1489
|
self.get_output_node(meta).page().runJavaScript(
|
|
1472
|
-
f"if (typeof window.appendToolOutput !== 'undefined') appendToolOutput({self.to_json(
|
|
1490
|
+
f"""if (typeof window.appendToolOutput !== 'undefined') appendToolOutput({self.to_json(
|
|
1491
|
+
self.sanitize_html(content)
|
|
1492
|
+
)});"""
|
|
1473
1493
|
)
|
|
1474
|
-
except Exception
|
|
1494
|
+
except Exception:
|
|
1475
1495
|
pass
|
|
1476
1496
|
|
|
1477
1497
|
def tool_output_update(
|
|
@@ -1487,9 +1507,11 @@ class Renderer(BaseRenderer):
|
|
|
1487
1507
|
"""
|
|
1488
1508
|
try:
|
|
1489
1509
|
self.get_output_node(meta).page().runJavaScript(
|
|
1490
|
-
f"if (typeof window.updateToolOutput !== 'undefined') updateToolOutput({self.to_json(
|
|
1510
|
+
f"""if (typeof window.updateToolOutput !== 'undefined') updateToolOutput({self.to_json(
|
|
1511
|
+
self.sanitize_html(content)
|
|
1512
|
+
)});"""
|
|
1491
1513
|
)
|
|
1492
|
-
except Exception
|
|
1514
|
+
except Exception:
|
|
1493
1515
|
pass
|
|
1494
1516
|
|
|
1495
1517
|
def tool_output_clear(self, meta: CtxMeta):
|
|
@@ -1502,7 +1524,7 @@ class Renderer(BaseRenderer):
|
|
|
1502
1524
|
self.get_output_node(meta).page().runJavaScript(
|
|
1503
1525
|
f"if (typeof window.clearToolOutput !== 'undefined') clearToolOutput();"
|
|
1504
1526
|
)
|
|
1505
|
-
except Exception
|
|
1527
|
+
except Exception:
|
|
1506
1528
|
pass
|
|
1507
1529
|
|
|
1508
1530
|
def tool_output_begin(self, meta: CtxMeta):
|
|
@@ -1515,7 +1537,7 @@ class Renderer(BaseRenderer):
|
|
|
1515
1537
|
self.get_output_node(meta).page().runJavaScript(
|
|
1516
1538
|
f"if (typeof window.beginToolOutput !== 'undefined') beginToolOutput();"
|
|
1517
1539
|
)
|
|
1518
|
-
except Exception
|
|
1540
|
+
except Exception:
|
|
1519
1541
|
pass
|
|
1520
1542
|
|
|
1521
1543
|
def tool_output_end(self):
|
|
@@ -1524,9 +1546,20 @@ class Renderer(BaseRenderer):
|
|
|
1524
1546
|
self.get_output_node().page().runJavaScript(
|
|
1525
1547
|
f"if (typeof window.endToolOutput !== 'undefined') endToolOutput();"
|
|
1526
1548
|
)
|
|
1527
|
-
except Exception
|
|
1549
|
+
except Exception:
|
|
1528
1550
|
pass
|
|
1529
1551
|
|
|
1552
|
+
def sanitize_html(self, html: str) -> str:
|
|
1553
|
+
"""
|
|
1554
|
+
Sanitize HTML to prevent XSS attacks
|
|
1555
|
+
|
|
1556
|
+
:param html: HTML string to sanitize
|
|
1557
|
+
:return: sanitized HTML string
|
|
1558
|
+
"""
|
|
1559
|
+
if not html:
|
|
1560
|
+
return ""
|
|
1561
|
+
return self.RE_AMP_LT_GT.sub(r'&\1;', html)
|
|
1562
|
+
|
|
1530
1563
|
def append_debug(
|
|
1531
1564
|
self,
|
|
1532
1565
|
ctx: CtxItem,
|
|
@@ -1539,6 +1572,7 @@ class Renderer(BaseRenderer):
|
|
|
1539
1572
|
:param ctx: context item
|
|
1540
1573
|
:param pid: context PID
|
|
1541
1574
|
:param title: debug title
|
|
1575
|
+
:return: HTML debug info
|
|
1542
1576
|
"""
|
|
1543
1577
|
if title is None:
|
|
1544
1578
|
title = "debug"
|
|
@@ -1555,6 +1589,115 @@ class Renderer(BaseRenderer):
|
|
|
1555
1589
|
def remove_pid(self, pid: int):
|
|
1556
1590
|
"""
|
|
1557
1591
|
Remove PID from renderer
|
|
1592
|
+
|
|
1593
|
+
:param pid: context PID
|
|
1558
1594
|
"""
|
|
1559
1595
|
if pid in self.pids:
|
|
1560
|
-
del self.pids[pid]
|
|
1596
|
+
del self.pids[pid]
|
|
1597
|
+
self._thr.pop(pid, None)
|
|
1598
|
+
|
|
1599
|
+
def _throttle_get(self, pid: int) -> dict:
|
|
1600
|
+
"""
|
|
1601
|
+
Return per-pid throttle state
|
|
1602
|
+
|
|
1603
|
+
:param pid: context PID
|
|
1604
|
+
:return: throttle state dictionary
|
|
1605
|
+
"""
|
|
1606
|
+
thr = self._thr.get(pid)
|
|
1607
|
+
if thr is None:
|
|
1608
|
+
thr = {"last": 0.0, "op": 0, "name": "", "replace_html": "", "append": [], "code": False}
|
|
1609
|
+
self._thr[pid] = thr
|
|
1610
|
+
return thr
|
|
1611
|
+
|
|
1612
|
+
def _throttle_reset(self, pid: Optional[int]):
|
|
1613
|
+
"""
|
|
1614
|
+
Reset throttle state
|
|
1615
|
+
|
|
1616
|
+
:param pid: context PID
|
|
1617
|
+
"""
|
|
1618
|
+
if pid is None:
|
|
1619
|
+
return
|
|
1620
|
+
thr = self._thr.get(pid)
|
|
1621
|
+
if thr is None:
|
|
1622
|
+
return
|
|
1623
|
+
thr["op"] = 0
|
|
1624
|
+
thr["name"] = ""
|
|
1625
|
+
thr["replace_html"] = ""
|
|
1626
|
+
thr["append"].clear()
|
|
1627
|
+
thr["code"] = False
|
|
1628
|
+
|
|
1629
|
+
def _throttle_queue(
|
|
1630
|
+
self,
|
|
1631
|
+
pid: int,
|
|
1632
|
+
name: str,
|
|
1633
|
+
html: str,
|
|
1634
|
+
text_chunk: str,
|
|
1635
|
+
replace: bool,
|
|
1636
|
+
is_code_block: bool
|
|
1637
|
+
):
|
|
1638
|
+
"""
|
|
1639
|
+
Queue chunk for throttled emit
|
|
1640
|
+
|
|
1641
|
+
:param pid: context PID
|
|
1642
|
+
:param name: name of the chunk
|
|
1643
|
+
:param html: HTML content of the chunk
|
|
1644
|
+
:param text_chunk: raw text chunk
|
|
1645
|
+
:param replace: whether to replace the current content
|
|
1646
|
+
:param is_code_block: whether the chunk is a code block
|
|
1647
|
+
"""
|
|
1648
|
+
thr = self._throttle_get(pid)
|
|
1649
|
+
if name:
|
|
1650
|
+
thr["name"] = name
|
|
1651
|
+
if replace:
|
|
1652
|
+
thr["op"] = 1
|
|
1653
|
+
thr["replace_html"] = html
|
|
1654
|
+
thr["append"].clear()
|
|
1655
|
+
thr["code"] = bool(is_code_block)
|
|
1656
|
+
else:
|
|
1657
|
+
if thr["op"] != 1:
|
|
1658
|
+
thr["op"] = 2
|
|
1659
|
+
thr["append"].append(text_chunk)
|
|
1660
|
+
thr["code"] = bool(is_code_block)
|
|
1661
|
+
|
|
1662
|
+
def _throttle_emit(self, pid: int, force: bool = False):
|
|
1663
|
+
"""
|
|
1664
|
+
Emit queued chunks if due
|
|
1665
|
+
|
|
1666
|
+
:param pid: context PID
|
|
1667
|
+
:param force: force emit regardless of throttle interval
|
|
1668
|
+
"""
|
|
1669
|
+
thr = self._throttle_get(pid)
|
|
1670
|
+
now = monotonic()
|
|
1671
|
+
if not force and (now - thr["last"] < self._throttle_interval):
|
|
1672
|
+
return
|
|
1673
|
+
if thr["op"] == 1:
|
|
1674
|
+
try:
|
|
1675
|
+
node = self.get_output_node_by_pid(pid)
|
|
1676
|
+
if node is not None:
|
|
1677
|
+
node.page().bridge.chunk.emit(
|
|
1678
|
+
thr["name"],
|
|
1679
|
+
self.sanitize_html(thr["replace_html"]),
|
|
1680
|
+
"",
|
|
1681
|
+
True,
|
|
1682
|
+
bool(thr["code"]),
|
|
1683
|
+
)
|
|
1684
|
+
except Exception:
|
|
1685
|
+
pass
|
|
1686
|
+
thr["last"] = now
|
|
1687
|
+
self._throttle_reset(pid)
|
|
1688
|
+
elif thr["op"] == 2 and thr["append"]:
|
|
1689
|
+
append_str = "".join(thr["append"])
|
|
1690
|
+
try:
|
|
1691
|
+
node = self.get_output_node_by_pid(pid)
|
|
1692
|
+
if node is not None:
|
|
1693
|
+
node.page().bridge.chunk.emit(
|
|
1694
|
+
thr["name"],
|
|
1695
|
+
"",
|
|
1696
|
+
self.sanitize_html(append_str),
|
|
1697
|
+
False,
|
|
1698
|
+
bool(thr["code"]),
|
|
1699
|
+
)
|
|
1700
|
+
except Exception:
|
|
1701
|
+
pass
|
|
1702
|
+
thr["last"] = now
|
|
1703
|
+
self._throttle_reset(pid)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"__meta__": {
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"app.version": "2.6.
|
|
5
|
-
"updated_at": "2025-08-
|
|
3
|
+
"version": "2.6.12",
|
|
4
|
+
"app.version": "2.6.12",
|
|
5
|
+
"updated_at": "2025-08-19T00: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.
|
|
4
|
-
"app.version": "2.6.
|
|
5
|
-
"updated_at": "2025-08-
|
|
3
|
+
"version": "2.6.12",
|
|
4
|
+
"app.version": "2.6.12",
|
|
5
|
+
"updated_at": "2025-08-19T23:07:35"
|
|
6
6
|
},
|
|
7
7
|
"items": {
|
|
8
8
|
"SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
|