pygpt-net 2.6.11__py3-none-any.whl → 2.6.13__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 +11 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +7 -1
- pygpt_net/controller/chat/output.py +2 -2
- pygpt_net/controller/chat/response.py +9 -4
- pygpt_net/controller/chat/stream.py +10 -4
- pygpt_net/controller/presets/editor.py +1 -1
- pygpt_net/core/agents/runner.py +2 -0
- pygpt_net/core/agents/runners/llama_workflow.py +13 -4
- pygpt_net/core/ctx/bag.py +2 -1
- pygpt_net/core/debug/debug.py +12 -3
- pygpt_net/core/experts/experts.py +1 -1
- pygpt_net/core/presets/presets.py +1 -2
- pygpt_net/core/render/web/body.py +52 -10
- pygpt_net/core/render/web/pid.py +20 -4
- pygpt_net/core/render/web/renderer.py +395 -88
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- 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/layout/toolbox/presets.py +0 -4
- pygpt_net/ui/main.py +5 -2
- pygpt_net/ui/widget/textarea/html.py +2 -7
- pygpt_net/ui/widget/textarea/web.py +35 -27
- pygpt_net/utils.py +15 -8
- {pygpt_net-2.6.11.dist-info → pygpt_net-2.6.13.dist-info}/METADATA +13 -2
- {pygpt_net-2.6.11.dist-info → pygpt_net-2.6.13.dist-info}/RECORD +32 -32
- {pygpt_net-2.6.11.dist-info → pygpt_net-2.6.13.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.11.dist-info → pygpt_net-2.6.13.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.11.dist-info → pygpt_net-2.6.13.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
|
|
@@ -57,7 +59,7 @@ class Renderer(BaseRenderer):
|
|
|
57
59
|
self.body = Body(window)
|
|
58
60
|
self.helpers = Helpers(window)
|
|
59
61
|
self.parser = Parser(window)
|
|
60
|
-
self.pids = {}
|
|
62
|
+
self.pids = {}
|
|
61
63
|
self.prev_chunk_replace = False
|
|
62
64
|
self.prev_chunk_newline = False
|
|
63
65
|
|
|
@@ -66,6 +68,9 @@ class Renderer(BaseRenderer):
|
|
|
66
68
|
self._icon_sync = os.path.join(app_path, "data", "icons", "sync.svg")
|
|
67
69
|
self._file_prefix = 'file:///' if self.window and self.window.core.platforms.is_windows() else 'file://'
|
|
68
70
|
|
|
71
|
+
self._thr = {}
|
|
72
|
+
self._throttle_interval = 0.01 # 10 ms delay
|
|
73
|
+
|
|
69
74
|
def prepare(self):
|
|
70
75
|
"""
|
|
71
76
|
Prepare renderer
|
|
@@ -103,9 +108,8 @@ class Renderer(BaseRenderer):
|
|
|
103
108
|
pid = tab.pid
|
|
104
109
|
if pid is None or pid not in self.pids:
|
|
105
110
|
return
|
|
106
|
-
self.pids[pid].loaded = True
|
|
107
|
-
node = self.get_output_node(meta)
|
|
108
111
|
|
|
112
|
+
self.pids[pid].loaded = True
|
|
109
113
|
if self.pids[pid].html != "" and not self.pids[pid].use_buffer:
|
|
110
114
|
self.clear_chunks_input(pid)
|
|
111
115
|
self.clear_chunks_output(pid)
|
|
@@ -113,8 +117,6 @@ class Renderer(BaseRenderer):
|
|
|
113
117
|
self.append(pid, self.pids[pid].html, flush=True)
|
|
114
118
|
self.pids[pid].html = ""
|
|
115
119
|
|
|
116
|
-
node.setUpdatesEnabled(True)
|
|
117
|
-
|
|
118
120
|
def get_pid(self, meta: CtxMeta):
|
|
119
121
|
"""
|
|
120
122
|
Get PID for context meta
|
|
@@ -188,7 +190,8 @@ class Renderer(BaseRenderer):
|
|
|
188
190
|
node = self.get_output_node_by_pid(pid)
|
|
189
191
|
try:
|
|
190
192
|
node.page().runJavaScript(
|
|
191
|
-
"if (typeof window.showLoading !== 'undefined') showLoading();"
|
|
193
|
+
"if (typeof window.showLoading !== 'undefined') showLoading();"
|
|
194
|
+
)
|
|
192
195
|
except Exception as e:
|
|
193
196
|
pass
|
|
194
197
|
|
|
@@ -198,7 +201,8 @@ class Renderer(BaseRenderer):
|
|
|
198
201
|
if node is not None:
|
|
199
202
|
try:
|
|
200
203
|
node.page().runJavaScript(
|
|
201
|
-
"if (typeof window.hideLoading !== 'undefined') hideLoading();"
|
|
204
|
+
"if (typeof window.hideLoading !== 'undefined') hideLoading();"
|
|
205
|
+
)
|
|
202
206
|
except Exception as e:
|
|
203
207
|
pass
|
|
204
208
|
|
|
@@ -208,7 +212,8 @@ class Renderer(BaseRenderer):
|
|
|
208
212
|
if node is not None:
|
|
209
213
|
try:
|
|
210
214
|
node.page().runJavaScript(
|
|
211
|
-
"if (typeof window.hideLoading !== 'undefined') hideLoading();"
|
|
215
|
+
"if (typeof window.hideLoading !== 'undefined') hideLoading();"
|
|
216
|
+
)
|
|
212
217
|
except Exception as e:
|
|
213
218
|
pass
|
|
214
219
|
|
|
@@ -250,6 +255,8 @@ class Renderer(BaseRenderer):
|
|
|
250
255
|
if self.pids[pid].item is not None and stream:
|
|
251
256
|
self.append_context_item(meta, self.pids[pid].item)
|
|
252
257
|
self.pids[pid].item = None
|
|
258
|
+
else:
|
|
259
|
+
self.reload()
|
|
253
260
|
self.pids[pid].clear()
|
|
254
261
|
|
|
255
262
|
def end_extra(
|
|
@@ -299,6 +306,9 @@ class Renderer(BaseRenderer):
|
|
|
299
306
|
pid = self.get_or_create_pid(meta)
|
|
300
307
|
if pid is None:
|
|
301
308
|
return
|
|
309
|
+
|
|
310
|
+
self._throttle_emit(pid, force=True)
|
|
311
|
+
self._throttle_reset(pid)
|
|
302
312
|
if self.window.controller.agent.legacy.enabled():
|
|
303
313
|
if self.pids[pid].item is not None:
|
|
304
314
|
self.append_context_item(meta, self.pids[pid].item)
|
|
@@ -316,7 +326,27 @@ class Renderer(BaseRenderer):
|
|
|
316
326
|
clear: bool = True
|
|
317
327
|
):
|
|
318
328
|
"""
|
|
319
|
-
Append all context to output
|
|
329
|
+
Append all context items to output
|
|
330
|
+
|
|
331
|
+
:param meta: Context meta
|
|
332
|
+
:param items: context items
|
|
333
|
+
:param clear: True if clear all output before append
|
|
334
|
+
"""
|
|
335
|
+
self.tool_output_end()
|
|
336
|
+
self.append_context_all(
|
|
337
|
+
meta,
|
|
338
|
+
items,
|
|
339
|
+
clear=clear,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def append_context_partial(
|
|
343
|
+
self,
|
|
344
|
+
meta: CtxMeta,
|
|
345
|
+
items: List[CtxItem],
|
|
346
|
+
clear: bool = True
|
|
347
|
+
):
|
|
348
|
+
"""
|
|
349
|
+
Append all context items to output (part by part)
|
|
320
350
|
|
|
321
351
|
:param meta: Context meta
|
|
322
352
|
:param items: context items
|
|
@@ -335,6 +365,7 @@ class Renderer(BaseRenderer):
|
|
|
335
365
|
self.pids[pid].use_buffer = True
|
|
336
366
|
self.pids[pid].html = ""
|
|
337
367
|
prev_ctx = None
|
|
368
|
+
next_item = None
|
|
338
369
|
total = len(items)
|
|
339
370
|
for i, item in enumerate(items):
|
|
340
371
|
self.update_names(meta, item)
|
|
@@ -346,43 +377,137 @@ class Renderer(BaseRenderer):
|
|
|
346
377
|
meta,
|
|
347
378
|
item,
|
|
348
379
|
prev_ctx=prev_ctx,
|
|
349
|
-
next_ctx=next_item
|
|
380
|
+
next_ctx=next_item,
|
|
350
381
|
)
|
|
351
382
|
prev_ctx = item
|
|
383
|
+
|
|
384
|
+
prev_ctx = None
|
|
385
|
+
next_item = None
|
|
352
386
|
self.pids[pid].use_buffer = False
|
|
387
|
+
if self.pids[pid].html != "":
|
|
388
|
+
self.append(
|
|
389
|
+
pid,
|
|
390
|
+
self.pids[pid].html,
|
|
391
|
+
flush=True,
|
|
392
|
+
)
|
|
393
|
+
self.parser.reset()
|
|
394
|
+
|
|
395
|
+
def append_context_all(
|
|
396
|
+
self,
|
|
397
|
+
meta: CtxMeta,
|
|
398
|
+
items: List[CtxItem],
|
|
399
|
+
clear: bool = True
|
|
400
|
+
):
|
|
401
|
+
"""
|
|
402
|
+
Append all context items to output (whole context at once)
|
|
403
|
+
|
|
404
|
+
:param meta: Context meta
|
|
405
|
+
:param items: context items
|
|
406
|
+
:param clear: True if clear all output before append
|
|
407
|
+
"""
|
|
408
|
+
if len(items) == 0:
|
|
409
|
+
if meta is None:
|
|
410
|
+
return
|
|
353
411
|
|
|
412
|
+
pid = self.get_or_create_pid(meta)
|
|
413
|
+
self.init(pid)
|
|
414
|
+
|
|
415
|
+
if clear:
|
|
416
|
+
self.reset(meta)
|
|
417
|
+
|
|
418
|
+
self.pids[pid].use_buffer = True
|
|
419
|
+
self.pids[pid].html = ""
|
|
420
|
+
prev_ctx = None
|
|
421
|
+
next_ctx = None
|
|
422
|
+
total = len(items)
|
|
423
|
+
html_parts = []
|
|
424
|
+
for i, item in enumerate(items):
|
|
425
|
+
self.update_names(meta, item)
|
|
426
|
+
item.idx = i
|
|
427
|
+
if i == 0:
|
|
428
|
+
item.first = True
|
|
429
|
+
next_ctx = items[i + 1] if i + 1 < total else None
|
|
430
|
+
|
|
431
|
+
# ignore hidden items
|
|
432
|
+
if item.hidden:
|
|
433
|
+
prev_ctx = item
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
# input node
|
|
437
|
+
data = self.prepare_input(meta, item, flush=False)
|
|
438
|
+
if data:
|
|
439
|
+
html = self.prepare_node(
|
|
440
|
+
meta=meta,
|
|
441
|
+
ctx=item,
|
|
442
|
+
html=data,
|
|
443
|
+
type=self.NODE_INPUT,
|
|
444
|
+
prev_ctx=prev_ctx,
|
|
445
|
+
next_ctx=next_ctx,
|
|
446
|
+
)
|
|
447
|
+
if html:
|
|
448
|
+
html_parts.append(html)
|
|
449
|
+
|
|
450
|
+
# output node
|
|
451
|
+
data = self.prepare_output(
|
|
452
|
+
meta,
|
|
453
|
+
item,
|
|
454
|
+
flush=False,
|
|
455
|
+
prev_ctx=prev_ctx,
|
|
456
|
+
next_ctx=next_ctx,
|
|
457
|
+
)
|
|
458
|
+
if data:
|
|
459
|
+
html = self.prepare_node(
|
|
460
|
+
meta=meta,
|
|
461
|
+
ctx=item,
|
|
462
|
+
html=data,
|
|
463
|
+
type=self.NODE_OUTPUT,
|
|
464
|
+
prev_ctx=prev_ctx,
|
|
465
|
+
next_ctx=next_ctx,
|
|
466
|
+
)
|
|
467
|
+
if html:
|
|
468
|
+
html_parts.append(html)
|
|
469
|
+
|
|
470
|
+
prev_ctx = item
|
|
471
|
+
|
|
472
|
+
# flush all nodes at once
|
|
473
|
+
if html_parts:
|
|
474
|
+
self.append(
|
|
475
|
+
pid,
|
|
476
|
+
"".join(html_parts)
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
html_parts.clear()
|
|
480
|
+
html_parts = None
|
|
481
|
+
prev_ctx = None
|
|
482
|
+
next_ctx = None
|
|
483
|
+
self.pids[pid].use_buffer = False
|
|
354
484
|
if self.pids[pid].html != "":
|
|
355
485
|
self.append(
|
|
356
486
|
pid,
|
|
357
487
|
self.pids[pid].html,
|
|
358
|
-
flush=True
|
|
488
|
+
flush=True,
|
|
359
489
|
)
|
|
490
|
+
self.parser.reset()
|
|
360
491
|
|
|
361
|
-
def
|
|
492
|
+
def prepare_input(
|
|
362
493
|
self, meta: CtxMeta,
|
|
363
494
|
ctx: CtxItem,
|
|
364
495
|
flush: bool = True,
|
|
365
496
|
append: bool = False
|
|
366
|
-
):
|
|
497
|
+
) -> Optional[str]:
|
|
367
498
|
"""
|
|
368
|
-
|
|
499
|
+
Prepare text input
|
|
369
500
|
|
|
370
501
|
:param meta: context meta
|
|
371
502
|
:param ctx: context item
|
|
372
503
|
:param flush: flush HTML
|
|
373
504
|
:param append: True if force append node
|
|
505
|
+
:return: Prepared input text or None if internal or empty input
|
|
374
506
|
"""
|
|
375
|
-
self.tool_output_end()
|
|
376
|
-
pid = self.get_or_create_pid(meta)
|
|
377
|
-
if not flush:
|
|
378
|
-
self.clear_chunks_input(pid)
|
|
379
|
-
|
|
380
|
-
self.update_names(meta, ctx)
|
|
381
507
|
if ctx.input is None or ctx.input == "":
|
|
382
508
|
return
|
|
383
509
|
|
|
384
510
|
text = ctx.input
|
|
385
|
-
|
|
386
511
|
if isinstance(ctx.extra, dict) and "sub_reply" in ctx.extra and ctx.extra["sub_reply"]:
|
|
387
512
|
try:
|
|
388
513
|
json_encoded = json.loads(text)
|
|
@@ -402,32 +527,60 @@ class Renderer(BaseRenderer):
|
|
|
402
527
|
if ctx.internal and ctx.input.startswith("user: "):
|
|
403
528
|
text = re.sub(r'^user: ', '> ', ctx.input)
|
|
404
529
|
|
|
405
|
-
|
|
406
|
-
if self.is_stream() and not append:
|
|
407
|
-
content = self.prepare_node(meta, ctx, text.strip(), self.NODE_INPUT)
|
|
408
|
-
self.append_chunk_input(meta, ctx, content, False)
|
|
409
|
-
return
|
|
530
|
+
return text.strip()
|
|
410
531
|
|
|
411
|
-
|
|
532
|
+
def append_input(
|
|
533
|
+
self, meta: CtxMeta,
|
|
534
|
+
ctx: CtxItem,
|
|
535
|
+
flush: bool = True,
|
|
536
|
+
append: bool = False
|
|
537
|
+
):
|
|
538
|
+
"""
|
|
539
|
+
Append text input to output
|
|
412
540
|
|
|
413
|
-
|
|
541
|
+
:param meta: context meta
|
|
542
|
+
:param ctx: context item
|
|
543
|
+
:param flush: flush HTML
|
|
544
|
+
:param append: True if force append node
|
|
545
|
+
"""
|
|
546
|
+
self.tool_output_end()
|
|
547
|
+
pid = self.get_or_create_pid(meta)
|
|
548
|
+
if not flush:
|
|
549
|
+
self.clear_chunks_input(pid)
|
|
550
|
+
|
|
551
|
+
self.update_names(meta, ctx)
|
|
552
|
+
text = self.prepare_input(meta, ctx, flush, append)
|
|
553
|
+
if text:
|
|
554
|
+
if flush:
|
|
555
|
+
if self.is_stream() and not append:
|
|
556
|
+
content = self.prepare_node(meta, ctx, text, self.NODE_INPUT)
|
|
557
|
+
self.append_chunk_input(meta, ctx, content, begin=False)
|
|
558
|
+
return
|
|
559
|
+
self.append_node(
|
|
560
|
+
meta=meta,
|
|
561
|
+
ctx=ctx,
|
|
562
|
+
html=text,
|
|
563
|
+
type=self.NODE_INPUT,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
def prepare_output(
|
|
414
567
|
self,
|
|
415
568
|
meta: CtxMeta,
|
|
416
569
|
ctx: CtxItem,
|
|
417
570
|
flush: bool = True,
|
|
418
571
|
prev_ctx: Optional[CtxItem] = None,
|
|
419
572
|
next_ctx: Optional[CtxItem] = None
|
|
420
|
-
):
|
|
573
|
+
) -> Optional[str]:
|
|
421
574
|
"""
|
|
422
|
-
|
|
575
|
+
Prepare text output
|
|
423
576
|
|
|
424
577
|
:param meta: context meta
|
|
425
578
|
:param ctx: context item
|
|
426
579
|
:param flush: flush HTML
|
|
427
580
|
:param prev_ctx: previous context
|
|
428
581
|
:param next_ctx: next context
|
|
582
|
+
:return: Prepared output text or None if empty output
|
|
429
583
|
"""
|
|
430
|
-
self.tool_output_end()
|
|
431
584
|
output = ctx.output
|
|
432
585
|
if isinstance(ctx.extra, dict) and ctx.extra.get("output"):
|
|
433
586
|
if self.window.core.config.get("llama.idx.chat.agent.render.all", False):
|
|
@@ -437,14 +590,42 @@ class Renderer(BaseRenderer):
|
|
|
437
590
|
else:
|
|
438
591
|
if not output:
|
|
439
592
|
return
|
|
440
|
-
|
|
593
|
+
return output.strip()
|
|
594
|
+
|
|
595
|
+
def append_output(
|
|
596
|
+
self,
|
|
597
|
+
meta: CtxMeta,
|
|
598
|
+
ctx: CtxItem,
|
|
599
|
+
flush: bool = True,
|
|
600
|
+
prev_ctx: Optional[CtxItem] = None,
|
|
601
|
+
next_ctx: Optional[CtxItem] = None
|
|
602
|
+
):
|
|
603
|
+
"""
|
|
604
|
+
Append text output to output
|
|
605
|
+
|
|
606
|
+
:param meta: context meta
|
|
607
|
+
:param ctx: context item
|
|
608
|
+
:param flush: flush HTML
|
|
609
|
+
:param prev_ctx: previous context
|
|
610
|
+
:param next_ctx: next context
|
|
611
|
+
"""
|
|
612
|
+
self.tool_output_end()
|
|
613
|
+
output = self.prepare_output(
|
|
441
614
|
meta=meta,
|
|
442
615
|
ctx=ctx,
|
|
443
|
-
|
|
444
|
-
type=self.NODE_OUTPUT,
|
|
616
|
+
flush=flush,
|
|
445
617
|
prev_ctx=prev_ctx,
|
|
446
|
-
next_ctx=next_ctx
|
|
618
|
+
next_ctx=next_ctx,
|
|
447
619
|
)
|
|
620
|
+
if output:
|
|
621
|
+
self.append_node(
|
|
622
|
+
meta=meta,
|
|
623
|
+
ctx=ctx,
|
|
624
|
+
html=output,
|
|
625
|
+
type=self.NODE_OUTPUT,
|
|
626
|
+
prev_ctx=prev_ctx,
|
|
627
|
+
next_ctx=next_ctx,
|
|
628
|
+
)
|
|
448
629
|
|
|
449
630
|
def append_chunk(
|
|
450
631
|
self,
|
|
@@ -467,10 +648,15 @@ class Renderer(BaseRenderer):
|
|
|
467
648
|
if not text_chunk:
|
|
468
649
|
if begin:
|
|
469
650
|
pctx.clear()
|
|
651
|
+
self._throttle_emit(pid, force=True)
|
|
652
|
+
self._throttle_reset(pid)
|
|
470
653
|
return
|
|
471
654
|
|
|
472
|
-
|
|
473
|
-
|
|
655
|
+
if begin: # prepare name and avatar header only at the beginning to avoid unnecessary checks
|
|
656
|
+
pctx.header = self.get_name_header(ctx)
|
|
657
|
+
self.update_names(meta, ctx)
|
|
658
|
+
|
|
659
|
+
name_header_str = pctx.header
|
|
474
660
|
text_chunk = text_chunk if isinstance(text_chunk, str) else str(text_chunk)
|
|
475
661
|
text_chunk = text_chunk.translate({ord('<'): '<', ord('>'): '>'})
|
|
476
662
|
|
|
@@ -479,8 +665,10 @@ class Renderer(BaseRenderer):
|
|
|
479
665
|
debug = self.append_debug(ctx, pid, "stream")
|
|
480
666
|
if debug:
|
|
481
667
|
text_chunk = debug + text_chunk
|
|
482
|
-
|
|
483
|
-
|
|
668
|
+
self._throttle_emit(pid, force=True)
|
|
669
|
+
self._throttle_reset(pid)
|
|
670
|
+
pctx.clear()
|
|
671
|
+
pctx.is_cmd = False
|
|
484
672
|
self.clear_chunks_output(pid)
|
|
485
673
|
self.prev_chunk_replace = False
|
|
486
674
|
|
|
@@ -496,11 +684,12 @@ class Renderer(BaseRenderer):
|
|
|
496
684
|
del buffer_to_parse
|
|
497
685
|
is_code_block = html.endswith(self.ENDINGS_CODE)
|
|
498
686
|
is_list = html.endswith(self.ENDINGS_LIST)
|
|
499
|
-
|
|
687
|
+
is_n = "\n" in text_chunk
|
|
688
|
+
is_newline = is_n or buffer.endswith("\n") or is_code_block
|
|
500
689
|
force_replace = False
|
|
501
690
|
if self.prev_chunk_newline:
|
|
502
691
|
force_replace = True
|
|
503
|
-
if
|
|
692
|
+
if is_n:
|
|
504
693
|
self.prev_chunk_newline = True
|
|
505
694
|
else:
|
|
506
695
|
self.prev_chunk_newline = False
|
|
@@ -509,38 +698,26 @@ class Renderer(BaseRenderer):
|
|
|
509
698
|
if is_newline or force_replace or is_list:
|
|
510
699
|
replace = True
|
|
511
700
|
if is_code_block:
|
|
512
|
-
|
|
513
|
-
if "\n" not in text_chunk:
|
|
514
|
-
# if there is no newline in raw_chunk, then don't replace
|
|
701
|
+
if not is_n:
|
|
515
702
|
replace = False
|
|
516
703
|
|
|
517
704
|
if not is_code_block:
|
|
518
|
-
|
|
705
|
+
if is_n:
|
|
706
|
+
text_chunk = text_chunk.replace("\n", "<br/>")
|
|
519
707
|
else:
|
|
520
|
-
if self.prev_chunk_replace and not has_unclosed_code_tag(text_chunk):
|
|
521
|
-
|
|
522
|
-
text_chunk = "".join(("\n", text_chunk)) # add newline to chunk
|
|
708
|
+
if self.prev_chunk_replace and (is_code_block and not has_unclosed_code_tag(text_chunk)):
|
|
709
|
+
text_chunk = "\n" + text_chunk
|
|
523
710
|
|
|
524
711
|
self.prev_chunk_replace = replace
|
|
525
712
|
|
|
526
|
-
# hide loading spinner if it is the beginning of the text
|
|
527
713
|
if begin:
|
|
528
714
|
try:
|
|
529
715
|
self.get_output_node(meta).page().runJavaScript("hideLoading();")
|
|
530
716
|
except Exception:
|
|
531
717
|
pass
|
|
532
718
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
self.get_output_node(meta).page().bridge.chunk.emit(
|
|
536
|
-
name_header_str or "",
|
|
537
|
-
self.sanitize_html(html) if replace else "",
|
|
538
|
-
self.sanitize_html(text_chunk) if not replace else "",
|
|
539
|
-
bool(replace),
|
|
540
|
-
bool(is_code_block),
|
|
541
|
-
)
|
|
542
|
-
except Exception:
|
|
543
|
-
pass
|
|
719
|
+
self._throttle_queue(pid, name_header_str or "", html, text_chunk, replace, is_code_block)
|
|
720
|
+
self._throttle_emit(pid, force=False)
|
|
544
721
|
|
|
545
722
|
def next_chunk(
|
|
546
723
|
self,
|
|
@@ -554,6 +731,8 @@ class Renderer(BaseRenderer):
|
|
|
554
731
|
:param ctx: context item
|
|
555
732
|
"""
|
|
556
733
|
pid = self.get_or_create_pid(meta)
|
|
734
|
+
self._throttle_emit(pid, force=True)
|
|
735
|
+
self._throttle_reset(pid)
|
|
557
736
|
self.pids[pid].item = ctx
|
|
558
737
|
self.pids[pid].buffer = ""
|
|
559
738
|
self.update_names(meta, ctx)
|
|
@@ -561,7 +740,8 @@ class Renderer(BaseRenderer):
|
|
|
561
740
|
self.prev_chunk_newline = False
|
|
562
741
|
try:
|
|
563
742
|
self.get_output_node(meta).page().runJavaScript(
|
|
564
|
-
"nextStream();"
|
|
743
|
+
"nextStream();"
|
|
744
|
+
)
|
|
565
745
|
except Exception:
|
|
566
746
|
pass
|
|
567
747
|
|
|
@@ -619,6 +799,7 @@ class Renderer(BaseRenderer):
|
|
|
619
799
|
if begin:
|
|
620
800
|
self.pids[pid].live_buffer = ""
|
|
621
801
|
return
|
|
802
|
+
|
|
622
803
|
self.update_names(meta, ctx)
|
|
623
804
|
raw_chunk = str(text_chunk).translate({ord('<'): '<', ord('>'): '>'})
|
|
624
805
|
if begin:
|
|
@@ -635,7 +816,6 @@ class Renderer(BaseRenderer):
|
|
|
635
816
|
to_append = self.pids[pid].live_buffer
|
|
636
817
|
if has_unclosed_code_tag(self.pids[pid].live_buffer):
|
|
637
818
|
to_append += "\n```"
|
|
638
|
-
print(to_append)
|
|
639
819
|
try:
|
|
640
820
|
self.get_output_node(meta).page().runJavaScript(
|
|
641
821
|
f"""replaceLive({self.to_json(
|
|
@@ -645,7 +825,6 @@ class Renderer(BaseRenderer):
|
|
|
645
825
|
)});"""
|
|
646
826
|
)
|
|
647
827
|
except Exception as e:
|
|
648
|
-
print(e)
|
|
649
828
|
pass
|
|
650
829
|
|
|
651
830
|
def clear_live(self, meta: CtxMeta, ctx: CtxItem):
|
|
@@ -698,7 +877,7 @@ class Renderer(BaseRenderer):
|
|
|
698
877
|
html=html,
|
|
699
878
|
type=type,
|
|
700
879
|
prev_ctx=prev_ctx,
|
|
701
|
-
next_ctx=next_ctx
|
|
880
|
+
next_ctx=next_ctx,
|
|
702
881
|
)
|
|
703
882
|
)
|
|
704
883
|
|
|
@@ -717,8 +896,9 @@ class Renderer(BaseRenderer):
|
|
|
717
896
|
"""
|
|
718
897
|
if self.pids[pid].loaded and not self.pids[pid].use_buffer:
|
|
719
898
|
self.clear_chunks(pid)
|
|
720
|
-
|
|
721
|
-
|
|
899
|
+
if html:
|
|
900
|
+
self.flush_output(pid, html)
|
|
901
|
+
self.pids[pid].clear()
|
|
722
902
|
else:
|
|
723
903
|
if not flush:
|
|
724
904
|
self.pids[pid].append_html(html)
|
|
@@ -741,14 +921,14 @@ class Renderer(BaseRenderer):
|
|
|
741
921
|
self.append_input(
|
|
742
922
|
meta,
|
|
743
923
|
ctx,
|
|
744
|
-
flush=False
|
|
924
|
+
flush=False,
|
|
745
925
|
)
|
|
746
926
|
self.append_output(
|
|
747
927
|
meta,
|
|
748
928
|
ctx,
|
|
749
929
|
flush=False,
|
|
750
930
|
prev_ctx=prev_ctx,
|
|
751
|
-
next_ctx=next_ctx
|
|
931
|
+
next_ctx=next_ctx,
|
|
752
932
|
)
|
|
753
933
|
|
|
754
934
|
def append_extra(
|
|
@@ -911,6 +1091,7 @@ class Renderer(BaseRenderer):
|
|
|
911
1091
|
node.reset_current_content()
|
|
912
1092
|
self.reset_names_by_pid(pid)
|
|
913
1093
|
self.prev_chunk_replace = False
|
|
1094
|
+
self._throttle_reset(pid)
|
|
914
1095
|
|
|
915
1096
|
def clear_input(self):
|
|
916
1097
|
"""Clear input"""
|
|
@@ -977,6 +1158,7 @@ class Renderer(BaseRenderer):
|
|
|
977
1158
|
self.get_output_node_by_pid(pid).page().runJavaScript(js)
|
|
978
1159
|
except Exception:
|
|
979
1160
|
pass
|
|
1161
|
+
self._throttle_reset(pid)
|
|
980
1162
|
|
|
981
1163
|
def clear_nodes(
|
|
982
1164
|
self,
|
|
@@ -1023,7 +1205,7 @@ class Renderer(BaseRenderer):
|
|
|
1023
1205
|
ctx=ctx,
|
|
1024
1206
|
html=html,
|
|
1025
1207
|
prev_ctx=prev_ctx,
|
|
1026
|
-
next_ctx=next_ctx
|
|
1208
|
+
next_ctx=next_ctx,
|
|
1027
1209
|
)
|
|
1028
1210
|
elif type == self.NODE_INPUT:
|
|
1029
1211
|
return self.prepare_node_input(
|
|
@@ -1031,7 +1213,7 @@ class Renderer(BaseRenderer):
|
|
|
1031
1213
|
ctx=ctx,
|
|
1032
1214
|
html=html,
|
|
1033
1215
|
prev_ctx=prev_ctx,
|
|
1034
|
-
next_ctx=next_ctx
|
|
1216
|
+
next_ctx=next_ctx,
|
|
1035
1217
|
)
|
|
1036
1218
|
|
|
1037
1219
|
def prepare_node_input(
|
|
@@ -1191,12 +1373,11 @@ class Renderer(BaseRenderer):
|
|
|
1191
1373
|
"""
|
|
1192
1374
|
try:
|
|
1193
1375
|
self.get_output_node_by_pid(pid).page().runJavaScript(
|
|
1194
|
-
f"""if (typeof window.appendNode !== 'undefined') appendNode({self.to_json(
|
|
1195
|
-
self.sanitize_html(html)
|
|
1196
|
-
)});"""
|
|
1376
|
+
f"""if (typeof window.appendNode !== 'undefined') appendNode({self.to_json(self.sanitize_html(html))});"""
|
|
1197
1377
|
)
|
|
1198
1378
|
except Exception:
|
|
1199
1379
|
pass
|
|
1380
|
+
html = None
|
|
1200
1381
|
|
|
1201
1382
|
def reload(self):
|
|
1202
1383
|
"""Reload output, called externally only on theme change to redraw content"""
|
|
@@ -1232,17 +1413,11 @@ class Renderer(BaseRenderer):
|
|
|
1232
1413
|
pid = self.get_or_create_pid(meta)
|
|
1233
1414
|
if pid is None:
|
|
1234
1415
|
return
|
|
1235
|
-
html = self.body.get_html(pid)
|
|
1236
|
-
self.pids[pid].loaded = False
|
|
1237
1416
|
node = self.get_output_node_by_pid(pid)
|
|
1238
1417
|
if node is not None:
|
|
1239
|
-
# hard reset
|
|
1240
|
-
# old_view = node
|
|
1241
|
-
# new_view = old_view.hard_reset()
|
|
1242
|
-
# self.window.ui.nodes['output'][pid] = new_view
|
|
1243
1418
|
node.resetPage()
|
|
1244
|
-
|
|
1245
|
-
self.
|
|
1419
|
+
|
|
1420
|
+
self._throttle_reset(pid)
|
|
1246
1421
|
|
|
1247
1422
|
def get_output_node(
|
|
1248
1423
|
self,
|
|
@@ -1284,7 +1459,8 @@ class Renderer(BaseRenderer):
|
|
|
1284
1459
|
"""
|
|
1285
1460
|
try:
|
|
1286
1461
|
self.get_output_node(ctx.meta).page().runJavaScript(
|
|
1287
|
-
f"if (typeof window.removeNode !== 'undefined') removeNode({self.to_json(ctx.id)});"
|
|
1462
|
+
f"if (typeof window.removeNode !== 'undefined') removeNode({self.to_json(ctx.id)});"
|
|
1463
|
+
)
|
|
1288
1464
|
except Exception:
|
|
1289
1465
|
pass
|
|
1290
1466
|
|
|
@@ -1296,7 +1472,8 @@ class Renderer(BaseRenderer):
|
|
|
1296
1472
|
"""
|
|
1297
1473
|
try:
|
|
1298
1474
|
self.get_output_node(ctx.meta).page().runJavaScript(
|
|
1299
|
-
f"if (typeof window.removeNodesFromId !== 'undefined') removeNodesFromId({self.to_json(ctx.id)});"
|
|
1475
|
+
f"if (typeof window.removeNodesFromId !== 'undefined') removeNodesFromId({self.to_json(ctx.id)});"
|
|
1476
|
+
)
|
|
1300
1477
|
except Exception:
|
|
1301
1478
|
pass
|
|
1302
1479
|
|
|
@@ -1421,6 +1598,7 @@ class Renderer(BaseRenderer):
|
|
|
1421
1598
|
self.clear_chunks(pid)
|
|
1422
1599
|
self.clear_nodes(pid)
|
|
1423
1600
|
self.pids[pid].html = ""
|
|
1601
|
+
self._throttle_reset(pid)
|
|
1424
1602
|
|
|
1425
1603
|
def scroll_to_bottom(self):
|
|
1426
1604
|
"""Scroll to bottom"""
|
|
@@ -1457,12 +1635,16 @@ class Renderer(BaseRenderer):
|
|
|
1457
1635
|
for node in nodes:
|
|
1458
1636
|
try:
|
|
1459
1637
|
node.page().runJavaScript(
|
|
1460
|
-
f"if (typeof window.updateCSS !== 'undefined') updateCSS({to_json});"
|
|
1638
|
+
f"if (typeof window.updateCSS !== 'undefined') updateCSS({to_json});"
|
|
1639
|
+
)
|
|
1461
1640
|
if self.window.core.config.get('render.blocks'):
|
|
1462
|
-
node.page().runJavaScript(
|
|
1641
|
+
node.page().runJavaScript(
|
|
1642
|
+
"if (typeof window.enableBlocks !== 'undefined') enableBlocks();"
|
|
1643
|
+
)
|
|
1463
1644
|
else:
|
|
1464
1645
|
node.page().runJavaScript(
|
|
1465
|
-
"if (typeof window.disableBlocks !== 'undefined') disableBlocks();"
|
|
1646
|
+
"if (typeof window.disableBlocks !== 'undefined') disableBlocks();"
|
|
1647
|
+
)
|
|
1466
1648
|
except Exception as e:
|
|
1467
1649
|
pass
|
|
1468
1650
|
return
|
|
@@ -1573,6 +1755,7 @@ class Renderer(BaseRenderer):
|
|
|
1573
1755
|
:param ctx: context item
|
|
1574
1756
|
:param pid: context PID
|
|
1575
1757
|
:param title: debug title
|
|
1758
|
+
:return: HTML debug info
|
|
1576
1759
|
"""
|
|
1577
1760
|
if title is None:
|
|
1578
1761
|
title = "debug"
|
|
@@ -1589,6 +1772,130 @@ class Renderer(BaseRenderer):
|
|
|
1589
1772
|
def remove_pid(self, pid: int):
|
|
1590
1773
|
"""
|
|
1591
1774
|
Remove PID from renderer
|
|
1775
|
+
|
|
1776
|
+
:param pid: context PID
|
|
1592
1777
|
"""
|
|
1593
1778
|
if pid in self.pids:
|
|
1594
|
-
del self.pids[pid]
|
|
1779
|
+
del self.pids[pid]
|
|
1780
|
+
self._thr.pop(pid, None)
|
|
1781
|
+
|
|
1782
|
+
def _throttle_get(self, pid: int) -> dict:
|
|
1783
|
+
"""
|
|
1784
|
+
Return per-pid throttle state
|
|
1785
|
+
|
|
1786
|
+
:param pid: context PID
|
|
1787
|
+
:return: throttle state dictionary
|
|
1788
|
+
"""
|
|
1789
|
+
thr = self._thr.get(pid)
|
|
1790
|
+
if thr is None:
|
|
1791
|
+
thr = {"last": 0.0, "op": 0, "name": "", "replace_html": "", "append": [], "code": False}
|
|
1792
|
+
self._thr[pid] = thr
|
|
1793
|
+
return thr
|
|
1794
|
+
|
|
1795
|
+
def _throttle_reset(self, pid: Optional[int]):
|
|
1796
|
+
"""
|
|
1797
|
+
Reset throttle state
|
|
1798
|
+
|
|
1799
|
+
:param pid: context PID
|
|
1800
|
+
"""
|
|
1801
|
+
if pid is None:
|
|
1802
|
+
return
|
|
1803
|
+
thr = self._thr.get(pid)
|
|
1804
|
+
if thr is None:
|
|
1805
|
+
return
|
|
1806
|
+
thr["op"] = 0
|
|
1807
|
+
thr["name"] = ""
|
|
1808
|
+
thr["replace_html"] = ""
|
|
1809
|
+
thr["append"].clear()
|
|
1810
|
+
thr["code"] = False
|
|
1811
|
+
|
|
1812
|
+
def _throttle_queue(
|
|
1813
|
+
self,
|
|
1814
|
+
pid: int,
|
|
1815
|
+
name: str,
|
|
1816
|
+
html: str,
|
|
1817
|
+
text_chunk: str,
|
|
1818
|
+
replace: bool,
|
|
1819
|
+
is_code_block: bool
|
|
1820
|
+
):
|
|
1821
|
+
"""
|
|
1822
|
+
Queue text chunk for throttled output
|
|
1823
|
+
|
|
1824
|
+
:param pid: context PID
|
|
1825
|
+
:param name: name header string
|
|
1826
|
+
:param html: HTML content to replace or append
|
|
1827
|
+
:param text_chunk: text chunk to append
|
|
1828
|
+
:param replace: True if the chunk should replace existing content
|
|
1829
|
+
:param is_code_block: True if the chunk is a code block
|
|
1830
|
+
"""
|
|
1831
|
+
thr = self._throttle_get(pid)
|
|
1832
|
+
if name:
|
|
1833
|
+
thr["name"] = name
|
|
1834
|
+
|
|
1835
|
+
if replace:
|
|
1836
|
+
thr["op"] = 1
|
|
1837
|
+
thr["replace_html"] = html
|
|
1838
|
+
thr["append"].clear()
|
|
1839
|
+
thr["code"] = bool(is_code_block)
|
|
1840
|
+
else:
|
|
1841
|
+
if thr["op"] == 1:
|
|
1842
|
+
thr["replace_html"] = html
|
|
1843
|
+
thr["code"] = bool(is_code_block)
|
|
1844
|
+
return
|
|
1845
|
+
thr["op"] = 2
|
|
1846
|
+
thr["append"].append(text_chunk)
|
|
1847
|
+
thr["code"] = bool(is_code_block)
|
|
1848
|
+
|
|
1849
|
+
def _throttle_emit(self, pid: int, force: bool = False):
|
|
1850
|
+
"""
|
|
1851
|
+
Emit throttled output to the node
|
|
1852
|
+
|
|
1853
|
+
:param pid: context PID
|
|
1854
|
+
:param force: Force emit even if throttle interval has not passed
|
|
1855
|
+
"""
|
|
1856
|
+
thr = self._throttle_get(pid)
|
|
1857
|
+
now = monotonic()
|
|
1858
|
+
if not force and (now - thr["last"] < self._throttle_interval):
|
|
1859
|
+
return
|
|
1860
|
+
|
|
1861
|
+
node = self.get_output_node_by_pid(pid)
|
|
1862
|
+
if node is None:
|
|
1863
|
+
return
|
|
1864
|
+
|
|
1865
|
+
try:
|
|
1866
|
+
if thr["op"] == 1:
|
|
1867
|
+
node.page().bridge.chunk.emit(
|
|
1868
|
+
thr["name"],
|
|
1869
|
+
self.sanitize_html(thr["replace_html"]),
|
|
1870
|
+
"",
|
|
1871
|
+
True,
|
|
1872
|
+
bool(thr["code"]),
|
|
1873
|
+
)
|
|
1874
|
+
thr["last"] = now
|
|
1875
|
+
|
|
1876
|
+
if thr["append"]:
|
|
1877
|
+
append_str = "".join(thr["append"])
|
|
1878
|
+
node.page().bridge.chunk.emit(
|
|
1879
|
+
thr["name"],
|
|
1880
|
+
"",
|
|
1881
|
+
self.sanitize_html(append_str),
|
|
1882
|
+
False,
|
|
1883
|
+
bool(thr["code"]),
|
|
1884
|
+
)
|
|
1885
|
+
thr["last"] = now
|
|
1886
|
+
|
|
1887
|
+
self._throttle_reset(pid)
|
|
1888
|
+
|
|
1889
|
+
elif thr["op"] == 2 and thr["append"]:
|
|
1890
|
+
append_str = "".join(thr["append"])
|
|
1891
|
+
node.page().bridge.chunk.emit(
|
|
1892
|
+
thr["name"],
|
|
1893
|
+
"",
|
|
1894
|
+
self.sanitize_html(append_str),
|
|
1895
|
+
False,
|
|
1896
|
+
bool(thr["code"]),
|
|
1897
|
+
)
|
|
1898
|
+
thr["last"] = now
|
|
1899
|
+
self._throttle_reset(pid)
|
|
1900
|
+
except Exception:
|
|
1901
|
+
pass
|