pygpt-net 2.6.34__py3-none-any.whl → 2.6.35__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 +7 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/chat/common.py +8 -2
- pygpt_net/controller/chat/handler/stream_worker.py +55 -43
- pygpt_net/controller/painter/common.py +13 -1
- pygpt_net/controller/painter/painter.py +11 -2
- pygpt_net/core/bridge/bridge.py +1 -5
- pygpt_net/core/bridge/context.py +81 -36
- pygpt_net/core/bridge/worker.py +3 -1
- pygpt_net/core/ctx/bag.py +4 -0
- pygpt_net/core/events/app.py +10 -17
- pygpt_net/core/events/base.py +17 -25
- pygpt_net/core/events/control.py +9 -17
- pygpt_net/core/events/event.py +9 -62
- pygpt_net/core/events/kernel.py +8 -17
- pygpt_net/core/events/realtime.py +8 -17
- pygpt_net/core/events/render.py +9 -17
- pygpt_net/core/render/web/body.py +394 -36
- pygpt_net/core/render/web/pid.py +39 -24
- pygpt_net/core/render/web/renderer.py +146 -40
- pygpt_net/data/config/config.json +4 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/web-blocks.css +3 -2
- pygpt_net/data/css/web-chatgpt.css +3 -1
- pygpt_net/data/css/web-chatgpt_wide.css +3 -1
- pygpt_net/data/locale/locale.de.ini +1 -0
- pygpt_net/data/locale/locale.en.ini +3 -2
- pygpt_net/data/locale/locale.es.ini +1 -0
- pygpt_net/data/locale/locale.fr.ini +1 -0
- pygpt_net/data/locale/locale.it.ini +1 -0
- pygpt_net/data/locale/locale.pl.ini +2 -1
- pygpt_net/data/locale/locale.uk.ini +1 -0
- pygpt_net/data/locale/locale.zh.ini +1 -0
- pygpt_net/provider/api/google/__init__.py +14 -5
- pygpt_net/provider/api/openai/__init__.py +13 -10
- pygpt_net/provider/core/config/patch.py +9 -0
- pygpt_net/ui/layout/chat/painter.py +63 -4
- pygpt_net/ui/widget/draw/painter.py +702 -106
- pygpt_net/ui/widget/textarea/web.py +2 -0
- {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/METADATA +9 -2
- {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/RECORD +44 -44
- {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/entry_points.txt +0 -0
pygpt_net/core/render/web/pid.py
CHANGED
|
@@ -6,37 +6,52 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.09.04 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import io
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
13
16
|
from pygpt_net.utils import trans
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
@dataclass(slots=True)
|
|
16
20
|
class PidData:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
"""Pid Data"""
|
|
22
|
+
# Required/primary data
|
|
23
|
+
pid: Any
|
|
24
|
+
meta: Optional[Any] = None
|
|
25
|
+
|
|
26
|
+
# Collections
|
|
27
|
+
images_appended: list = field(default_factory=list)
|
|
28
|
+
urls_appended: list = field(default_factory=list)
|
|
29
|
+
files_appended: list = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
# Internal buffers (excluded from repr to avoid large dumps)
|
|
32
|
+
_buffer: io.StringIO = field(default_factory=io.StringIO, repr=False)
|
|
33
|
+
_live_buffer: io.StringIO = field(default_factory=io.StringIO, repr=False)
|
|
34
|
+
_html: io.StringIO = field(default_factory=io.StringIO, repr=False)
|
|
35
|
+
_document: io.StringIO = field(default_factory=io.StringIO, repr=False)
|
|
36
|
+
|
|
37
|
+
# Flags/state
|
|
38
|
+
is_cmd: bool = False
|
|
39
|
+
initialized: bool = False
|
|
40
|
+
loaded: bool = False
|
|
41
|
+
item: Optional[Any] = None
|
|
42
|
+
use_buffer: bool = False
|
|
43
|
+
|
|
44
|
+
# Names
|
|
45
|
+
name_user: str = field(default_factory=lambda: trans("chat.name.user"))
|
|
46
|
+
name_bot: str = field(default_factory=lambda: trans("chat.name.bot"))
|
|
47
|
+
|
|
48
|
+
# Throttling / timing
|
|
49
|
+
last_time_called: float = 0.0
|
|
50
|
+
cooldown: float = 1 / 6
|
|
51
|
+
throttling_min_chars: int = 5000
|
|
52
|
+
|
|
53
|
+
# Misc
|
|
54
|
+
header: Optional[Any] = None
|
|
40
55
|
|
|
41
56
|
@property
|
|
42
57
|
def buffer(self) -> str:
|
|
@@ -6,16 +6,19 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.09.04 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
|
+
import gc
|
|
16
|
+
from dataclasses import dataclass, field
|
|
15
17
|
|
|
16
18
|
from datetime import datetime
|
|
17
19
|
from typing import Optional, List, Any
|
|
18
20
|
from time import monotonic
|
|
21
|
+
from io import StringIO
|
|
19
22
|
|
|
20
23
|
from pygpt_net.core.render.base import BaseRenderer
|
|
21
24
|
from pygpt_net.core.text.utils import has_unclosed_code_tag
|
|
@@ -48,6 +51,46 @@ class Renderer(BaseRenderer):
|
|
|
48
51
|
)
|
|
49
52
|
RE_AMP_LT_GT = re.compile(r'&(lt|gt);')
|
|
50
53
|
|
|
54
|
+
@dataclass(slots=True)
|
|
55
|
+
class _AppendBuffer:
|
|
56
|
+
"""Small, allocation-friendly buffer for throttled appends."""
|
|
57
|
+
_buf: StringIO = field(default_factory=StringIO, repr=False)
|
|
58
|
+
_size: int = 0
|
|
59
|
+
|
|
60
|
+
def append(self, s: str) -> None:
|
|
61
|
+
if not s:
|
|
62
|
+
return
|
|
63
|
+
self._buf.write(s)
|
|
64
|
+
self._size += len(s)
|
|
65
|
+
|
|
66
|
+
def is_empty(self) -> bool:
|
|
67
|
+
return self._size == 0
|
|
68
|
+
|
|
69
|
+
def get_and_clear(self) -> str:
|
|
70
|
+
"""Return content and replace underlying buffer to release memory eagerly."""
|
|
71
|
+
if self._size == 0:
|
|
72
|
+
return ""
|
|
73
|
+
data = self._buf.getvalue()
|
|
74
|
+
old = self._buf
|
|
75
|
+
# Replace the internal buffer instance to drop capacity immediately
|
|
76
|
+
self._buf = StringIO()
|
|
77
|
+
self._size = 0
|
|
78
|
+
try:
|
|
79
|
+
old.close()
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
def clear(self) -> None:
|
|
85
|
+
"""Clear content and drop buffer capacity."""
|
|
86
|
+
old = self._buf
|
|
87
|
+
self._buf = StringIO()
|
|
88
|
+
self._size = 0
|
|
89
|
+
try:
|
|
90
|
+
old.close()
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
|
|
51
94
|
def __init__(self, window=None):
|
|
52
95
|
super(Renderer, self).__init__(window)
|
|
53
96
|
"""
|
|
@@ -69,7 +112,7 @@ class Renderer(BaseRenderer):
|
|
|
69
112
|
self._file_prefix = 'file:///' if self.window and self.window.core.platforms.is_windows() else 'file://'
|
|
70
113
|
|
|
71
114
|
self._thr = {}
|
|
72
|
-
self._throttle_interval = 0.
|
|
115
|
+
self._throttle_interval = 0.03 # 30 ms delay
|
|
73
116
|
|
|
74
117
|
def prepare(self):
|
|
75
118
|
"""
|
|
@@ -328,6 +371,9 @@ class Renderer(BaseRenderer):
|
|
|
328
371
|
except Exception:
|
|
329
372
|
pass
|
|
330
373
|
|
|
374
|
+
# release strings
|
|
375
|
+
gc.collect()
|
|
376
|
+
|
|
331
377
|
def append_context(
|
|
332
378
|
self,
|
|
333
379
|
meta: CtxMeta,
|
|
@@ -597,7 +643,7 @@ class Renderer(BaseRenderer):
|
|
|
597
643
|
if self.window.core.config.get("agent.output.render.all", False):
|
|
598
644
|
output = ctx.output # full agent output
|
|
599
645
|
else:
|
|
600
|
-
output = ctx.extra["output"]
|
|
646
|
+
output = ctx.extra["output"] # final output only
|
|
601
647
|
else:
|
|
602
648
|
if not output:
|
|
603
649
|
return
|
|
@@ -656,6 +702,7 @@ class Renderer(BaseRenderer):
|
|
|
656
702
|
pid = self.get_or_create_pid(meta)
|
|
657
703
|
pctx = self.pids[pid]
|
|
658
704
|
pctx.item = ctx
|
|
705
|
+
|
|
659
706
|
if not text_chunk:
|
|
660
707
|
if begin:
|
|
661
708
|
pctx.clear()
|
|
@@ -663,13 +710,16 @@ class Renderer(BaseRenderer):
|
|
|
663
710
|
self._throttle_reset(pid)
|
|
664
711
|
return
|
|
665
712
|
|
|
666
|
-
if begin:
|
|
713
|
+
if begin:
|
|
714
|
+
# Prepare name header once per streaming session
|
|
667
715
|
pctx.header = self.get_name_header(ctx, stream=True)
|
|
668
716
|
self.update_names(meta, ctx)
|
|
669
717
|
|
|
670
718
|
name_header_str = pctx.header
|
|
671
719
|
text_chunk = text_chunk if isinstance(text_chunk, str) else str(text_chunk)
|
|
672
|
-
|
|
720
|
+
# Escape angle brackets only if present to avoid unnecessary allocations
|
|
721
|
+
if ('<' in text_chunk) or ('>' in text_chunk):
|
|
722
|
+
text_chunk = text_chunk.translate({ord('<'): '<', ord('>'): '>'})
|
|
673
723
|
|
|
674
724
|
if begin:
|
|
675
725
|
if self.is_debug():
|
|
@@ -683,42 +733,69 @@ class Renderer(BaseRenderer):
|
|
|
683
733
|
self.clear_chunks_output(pid)
|
|
684
734
|
self.prev_chunk_replace = False
|
|
685
735
|
|
|
736
|
+
# Append to the logical buffer (owned by pid)
|
|
686
737
|
pctx.append_buffer(text_chunk)
|
|
687
|
-
|
|
688
738
|
buffer = pctx.buffer
|
|
689
|
-
if has_unclosed_code_tag(buffer):
|
|
690
|
-
buffer_to_parse = "".join((buffer, "\n```"))
|
|
691
|
-
else:
|
|
692
|
-
buffer_to_parse = buffer
|
|
693
739
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
740
|
+
# Cheap detection of open code fence without full parse
|
|
741
|
+
open_code = has_unclosed_code_tag(buffer)
|
|
742
|
+
|
|
743
|
+
# Newline/flow state
|
|
698
744
|
is_n = "\n" in text_chunk
|
|
699
|
-
is_newline = is_n or buffer.endswith("\n") or
|
|
700
|
-
force_replace =
|
|
701
|
-
|
|
702
|
-
force_replace = True
|
|
703
|
-
if is_n:
|
|
704
|
-
self.prev_chunk_newline = True
|
|
705
|
-
else:
|
|
706
|
-
self.prev_chunk_newline = False
|
|
745
|
+
is_newline = is_n or buffer.endswith("\n") or open_code
|
|
746
|
+
force_replace = self.prev_chunk_newline
|
|
747
|
+
self.prev_chunk_newline = bool(is_n)
|
|
707
748
|
|
|
708
749
|
replace = False
|
|
709
|
-
if is_newline or force_replace
|
|
750
|
+
if is_newline or force_replace:
|
|
710
751
|
replace = True
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
752
|
+
# Do not replace for an open code block unless a newline arrived
|
|
753
|
+
if open_code and not is_n:
|
|
754
|
+
replace = False
|
|
755
|
+
|
|
756
|
+
thr = self._throttle_get(pid)
|
|
757
|
+
html = None
|
|
714
758
|
|
|
759
|
+
# Only parse when required:
|
|
760
|
+
# - a replace is needed now, or
|
|
761
|
+
# - a replace is pending in the throttle and must be refreshed, or
|
|
762
|
+
# - rarely: we must detect list termination without a newline
|
|
763
|
+
need_parse_for_pending_replace = (thr["op"] == 1)
|
|
764
|
+
need_parse_for_list = False
|
|
765
|
+
|
|
766
|
+
if not replace:
|
|
767
|
+
# Very rare case: list closing without newline. Check on a short tail only.
|
|
768
|
+
# This keeps behavior intact while avoiding full-buffer parse on every chunk.
|
|
769
|
+
tail = buffer[-4096:]
|
|
770
|
+
if tail:
|
|
771
|
+
tail_to_parse = f"{tail}\n```" if open_code else tail
|
|
772
|
+
tail_html = self.parser.parse(tail_to_parse)
|
|
773
|
+
need_parse_for_list = tail_html.endswith(self.ENDINGS_LIST)
|
|
774
|
+
# Tail string is short and will be collected promptly
|
|
775
|
+
del tail_html
|
|
776
|
+
if need_parse_for_list:
|
|
777
|
+
replace = True
|
|
778
|
+
|
|
779
|
+
if replace or need_parse_for_pending_replace:
|
|
780
|
+
buffer_to_parse = f"{buffer}\n```" if open_code else buffer
|
|
781
|
+
html = self.parser.parse(buffer_to_parse)
|
|
782
|
+
# Help the GC by breaking the reference as soon as possible
|
|
783
|
+
del buffer_to_parse
|
|
784
|
+
|
|
785
|
+
is_code_block = open_code
|
|
786
|
+
|
|
787
|
+
# Adjust output chunk formatting based on block type
|
|
715
788
|
if not is_code_block:
|
|
716
789
|
if is_n:
|
|
790
|
+
# Convert text newlines to <br/> in non-code context
|
|
717
791
|
text_chunk = text_chunk.replace("\n", "<br/>")
|
|
718
792
|
else:
|
|
793
|
+
# When previous operation replaced content and this chunk closes the fence,
|
|
794
|
+
# prepend a newline so the final code block renders correctly.
|
|
719
795
|
if self.prev_chunk_replace and (is_code_block and not has_unclosed_code_tag(text_chunk)):
|
|
720
796
|
text_chunk = "\n" + text_chunk
|
|
721
797
|
|
|
798
|
+
# Update replace flag for next iteration AFTER formatting decisions
|
|
722
799
|
self.prev_chunk_replace = replace
|
|
723
800
|
|
|
724
801
|
if begin:
|
|
@@ -727,7 +804,19 @@ class Renderer(BaseRenderer):
|
|
|
727
804
|
except Exception:
|
|
728
805
|
pass
|
|
729
806
|
|
|
730
|
-
|
|
807
|
+
# Queue throttled emission; HTML is only provided when it is really needed
|
|
808
|
+
self._throttle_queue(
|
|
809
|
+
pid=pid,
|
|
810
|
+
name=name_header_str or "",
|
|
811
|
+
html=html if html is not None else "",
|
|
812
|
+
text_chunk=text_chunk,
|
|
813
|
+
replace=replace,
|
|
814
|
+
is_code_block=is_code_block,
|
|
815
|
+
)
|
|
816
|
+
# Explicitly drop local ref to large html string as early as possible
|
|
817
|
+
html = None
|
|
818
|
+
|
|
819
|
+
# Emit if throttle interval allows
|
|
731
820
|
self._throttle_emit(pid, force=False)
|
|
732
821
|
|
|
733
822
|
def next_chunk(
|
|
@@ -1276,7 +1365,7 @@ class Renderer(BaseRenderer):
|
|
|
1276
1365
|
extra = ctx.extra["footer"]
|
|
1277
1366
|
extra_style = "display:block;"
|
|
1278
1367
|
|
|
1279
|
-
return
|
|
1368
|
+
return f'<div class="msg-box msg-user" id="{msg_id}"><div class="name-header name-user">{name}</div><div class="msg">{html}<div class="msg-extra" style="{extra_style}">{extra}</div>{debug}</div></div>'
|
|
1280
1369
|
|
|
1281
1370
|
def prepare_node_output(
|
|
1282
1371
|
self,
|
|
@@ -1289,7 +1378,7 @@ class Renderer(BaseRenderer):
|
|
|
1289
1378
|
"""
|
|
1290
1379
|
Prepare output node
|
|
1291
1380
|
|
|
1292
|
-
:param meta:
|
|
1381
|
+
:param meta: CtxMeta
|
|
1293
1382
|
:param ctx: CtxItem instance
|
|
1294
1383
|
:param html: html text
|
|
1295
1384
|
:param prev_ctx: previous context item
|
|
@@ -1399,12 +1488,12 @@ class Renderer(BaseRenderer):
|
|
|
1399
1488
|
"""
|
|
1400
1489
|
try:
|
|
1401
1490
|
if replace:
|
|
1402
|
-
self.get_output_node_by_pid(pid).page().
|
|
1403
|
-
|
|
1491
|
+
self.get_output_node_by_pid(pid).page().bridge.nodeReplace.emit(
|
|
1492
|
+
self.sanitize_html(html)
|
|
1404
1493
|
)
|
|
1405
1494
|
else:
|
|
1406
|
-
self.get_output_node_by_pid(pid).page().
|
|
1407
|
-
|
|
1495
|
+
self.get_output_node_by_pid(pid).page().bridge.node.emit(
|
|
1496
|
+
self.sanitize_html(html)
|
|
1408
1497
|
)
|
|
1409
1498
|
except Exception:
|
|
1410
1499
|
pass
|
|
@@ -1772,6 +1861,9 @@ class Renderer(BaseRenderer):
|
|
|
1772
1861
|
"""
|
|
1773
1862
|
if not html:
|
|
1774
1863
|
return ""
|
|
1864
|
+
# Fast path: avoid regex work and extra allocations when not needed
|
|
1865
|
+
if '&' not in html:
|
|
1866
|
+
return html
|
|
1775
1867
|
return self.RE_AMP_LT_GT.sub(r'&\1;', html)
|
|
1776
1868
|
|
|
1777
1869
|
def append_debug(
|
|
@@ -1819,7 +1911,14 @@ class Renderer(BaseRenderer):
|
|
|
1819
1911
|
"""
|
|
1820
1912
|
thr = self._thr.get(pid)
|
|
1821
1913
|
if thr is None:
|
|
1822
|
-
thr = {
|
|
1914
|
+
thr = {
|
|
1915
|
+
"last": 0.0,
|
|
1916
|
+
"op": 0,
|
|
1917
|
+
"name": "",
|
|
1918
|
+
"replace_html": "",
|
|
1919
|
+
"append": Renderer._AppendBuffer(),
|
|
1920
|
+
"code": False,
|
|
1921
|
+
}
|
|
1823
1922
|
self._thr[pid] = thr
|
|
1824
1923
|
return thr
|
|
1825
1924
|
|
|
@@ -1837,7 +1936,8 @@ class Renderer(BaseRenderer):
|
|
|
1837
1936
|
thr["op"] = 0
|
|
1838
1937
|
thr["name"] = ""
|
|
1839
1938
|
thr["replace_html"] = ""
|
|
1840
|
-
|
|
1939
|
+
# Replace append buffer instance to drop any capacity eagerly
|
|
1940
|
+
thr["append"] = Renderer._AppendBuffer()
|
|
1841
1941
|
thr["code"] = False
|
|
1842
1942
|
|
|
1843
1943
|
def _throttle_queue(
|
|
@@ -1866,10 +1966,12 @@ class Renderer(BaseRenderer):
|
|
|
1866
1966
|
if replace:
|
|
1867
1967
|
thr["op"] = 1
|
|
1868
1968
|
thr["replace_html"] = html
|
|
1969
|
+
# Drop previous append items aggressively when a replace snapshot is available
|
|
1869
1970
|
thr["append"].clear()
|
|
1870
1971
|
thr["code"] = bool(is_code_block)
|
|
1871
1972
|
else:
|
|
1872
1973
|
if thr["op"] == 1:
|
|
1974
|
+
# Refresh the pending replace with the latest HTML snapshot
|
|
1873
1975
|
thr["replace_html"] = html
|
|
1874
1976
|
thr["code"] = bool(is_code_block)
|
|
1875
1977
|
return
|
|
@@ -1895,17 +1997,21 @@ class Renderer(BaseRenderer):
|
|
|
1895
1997
|
|
|
1896
1998
|
try:
|
|
1897
1999
|
if thr["op"] == 1:
|
|
2000
|
+
# Replace snapshot
|
|
2001
|
+
replace_payload = self.sanitize_html(thr["replace_html"])
|
|
1898
2002
|
node.page().bridge.chunk.emit(
|
|
1899
2003
|
thr["name"],
|
|
1900
|
-
|
|
2004
|
+
replace_payload,
|
|
1901
2005
|
"",
|
|
1902
2006
|
True,
|
|
1903
2007
|
bool(thr["code"]),
|
|
1904
2008
|
)
|
|
2009
|
+
thr["replace_html"] = "" # Cut reference ASAP
|
|
1905
2010
|
thr["last"] = now
|
|
1906
2011
|
|
|
1907
|
-
if
|
|
1908
|
-
|
|
2012
|
+
# Append tail (if any)
|
|
2013
|
+
if not thr["append"].is_empty():
|
|
2014
|
+
append_str = thr["append"].get_and_clear()
|
|
1909
2015
|
node.page().bridge.chunk.emit(
|
|
1910
2016
|
thr["name"],
|
|
1911
2017
|
"",
|
|
@@ -1917,8 +2023,8 @@ class Renderer(BaseRenderer):
|
|
|
1917
2023
|
|
|
1918
2024
|
self._throttle_reset(pid)
|
|
1919
2025
|
|
|
1920
|
-
elif thr["op"] == 2 and thr["append"]:
|
|
1921
|
-
append_str =
|
|
2026
|
+
elif thr["op"] == 2 and not thr["append"].is_empty():
|
|
2027
|
+
append_str = thr["append"].get_and_clear()
|
|
1922
2028
|
node.page().bridge.chunk.emit(
|
|
1923
2029
|
thr["name"],
|
|
1924
2030
|
"",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"__meta__": {
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"app.version": "2.6.
|
|
5
|
-
"updated_at": "2025-09-
|
|
3
|
+
"version": "2.6.35",
|
|
4
|
+
"app.version": "2.6.35",
|
|
5
|
+
"updated_at": "2025-09-04T00:00:00"
|
|
6
6
|
},
|
|
7
7
|
"access.audio.event.speech": false,
|
|
8
8
|
"access.audio.event.speech.disabled": [],
|
|
@@ -354,6 +354,7 @@
|
|
|
354
354
|
"painter.brush.mode": "brush",
|
|
355
355
|
"painter.brush.size": 3,
|
|
356
356
|
"painter.canvas.size": "1280x720",
|
|
357
|
+
"painter.zoom": 100,
|
|
357
358
|
"personalize.about": "",
|
|
358
359
|
"personalize.modes": "chat",
|
|
359
360
|
"plugins": {},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"__meta__": {
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"app.version": "2.6.
|
|
5
|
-
"updated_at": "2025-09-
|
|
3
|
+
"version": "2.6.35",
|
|
4
|
+
"app.version": "2.6.35",
|
|
5
|
+
"updated_at": "2025-09-04T08:03:34"
|
|
6
6
|
},
|
|
7
7
|
"items": {
|
|
8
8
|
"SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
|
|
@@ -13,7 +13,6 @@ body {{
|
|
|
13
13
|
word-wrap: break-word;
|
|
14
14
|
padding: 6px;
|
|
15
15
|
line-height: 1.25;
|
|
16
|
-
will-change: transform, opacity;
|
|
17
16
|
margin: 4px;
|
|
18
17
|
padding: 0;
|
|
19
18
|
max-width: 100%;
|
|
@@ -26,10 +25,12 @@ body {{
|
|
|
26
25
|
-webkit-border-radius: 1ex;
|
|
27
26
|
-webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.75);
|
|
28
27
|
}}
|
|
28
|
+
#container {{
|
|
29
|
+
will-change: transform, opacity;
|
|
30
|
+
}}
|
|
29
31
|
.ts {{
|
|
30
32
|
display: none;
|
|
31
33
|
}}
|
|
32
|
-
|
|
33
34
|
/* base */
|
|
34
35
|
a {{
|
|
35
36
|
text-decoration: none;
|
|
@@ -13,7 +13,6 @@ body {{
|
|
|
13
13
|
word-wrap: break-word;
|
|
14
14
|
padding: 6px;
|
|
15
15
|
line-height: 1.25;
|
|
16
|
-
will-change: transform, opacity;
|
|
17
16
|
padding: 0;
|
|
18
17
|
margin: auto;
|
|
19
18
|
max-width: 720px;
|
|
@@ -27,6 +26,9 @@ body {{
|
|
|
27
26
|
-webkit-border-radius: 1ex;
|
|
28
27
|
-webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.75);
|
|
29
28
|
}}
|
|
29
|
+
#container {{
|
|
30
|
+
will-change: transform, opacity;
|
|
31
|
+
}}
|
|
30
32
|
.ts {{
|
|
31
33
|
display: none;
|
|
32
34
|
}}
|
|
@@ -13,7 +13,6 @@ body {{
|
|
|
13
13
|
word-wrap: break-word;
|
|
14
14
|
padding: 6px;
|
|
15
15
|
line-height: 1.25;
|
|
16
|
-
will-change: transform, opacity;
|
|
17
16
|
padding: 0;
|
|
18
17
|
margin: auto;
|
|
19
18
|
max-width: 100%;
|
|
@@ -27,6 +26,9 @@ body {{
|
|
|
27
26
|
-webkit-border-radius: 1ex;
|
|
28
27
|
-webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.75);
|
|
29
28
|
}}
|
|
29
|
+
#container {{
|
|
30
|
+
will-change: transform, opacity;
|
|
31
|
+
}}
|
|
30
32
|
.ts {{
|
|
31
33
|
display: none;
|
|
32
34
|
}}
|
|
@@ -961,6 +961,7 @@ painter.btn.camera.capture = Von der Kamera
|
|
|
961
961
|
painter.btn.capture = Bild erfassen
|
|
962
962
|
painter.btn.clear = Löschen
|
|
963
963
|
painter.btn.crop = Zuschneiden
|
|
964
|
+
painter.btn.fit = Anpassen
|
|
964
965
|
painter.capture.manual.captured.success = Bild erfasst:
|
|
965
966
|
painter.capture.name.prefix = Zeichnung von
|
|
966
967
|
painter.mode.erase = Radieren
|
|
@@ -147,6 +147,7 @@ assistant.store.expire_days.desc = 0 = Never
|
|
|
147
147
|
assistant.store.files.suffix = files
|
|
148
148
|
assistant.store.hide_threads = Hide threads vector stores
|
|
149
149
|
assistant.store.id = ID
|
|
150
|
+
assistant.store.menu.file.delete = Delete file
|
|
150
151
|
assistant.store.name = Name
|
|
151
152
|
assistant.store.status = Status
|
|
152
153
|
assistant.store.thread_only = (current thread only)
|
|
@@ -250,10 +251,10 @@ confirm.assistant.import = Import all assistants from API?
|
|
|
250
251
|
confirm.assistant.import_files = Import all files from API?
|
|
251
252
|
confirm.assistant.import_files.store = Import current store files from API?
|
|
252
253
|
confirm.assistant.store.clear = Clear vector stores (local only)?
|
|
254
|
+
confirm.assistant.store.file.delete = Delete selected file in API?
|
|
253
255
|
confirm.assistant.store.import = Import all vector stores from API?
|
|
254
256
|
confirm.assistant.store.refresh = Refresh all stores?
|
|
255
257
|
confirm.assistant.store.truncate = Delete all vector stores in API?
|
|
256
|
-
confirm.assistant.store.file.delete = Delete selected file in API?
|
|
257
258
|
confirm.ctx.delete = Delete group?
|
|
258
259
|
confirm.ctx.delete.all = Delete group and all items?
|
|
259
260
|
confirm.img.delete = Delete file from disk?
|
|
@@ -963,6 +964,7 @@ painter.btn.camera.capture = From camera
|
|
|
963
964
|
painter.btn.capture = Use image
|
|
964
965
|
painter.btn.clear = Clear
|
|
965
966
|
painter.btn.crop = Crop
|
|
967
|
+
painter.btn.fit = Fit
|
|
966
968
|
painter.capture.manual.captured.success = Image captured:
|
|
967
969
|
painter.capture.name.prefix = Drawing from
|
|
968
970
|
painter.mode.erase = Erase
|
|
@@ -1614,4 +1616,3 @@ vision.capture.manual.captured.success = Image captured from the camera:
|
|
|
1614
1616
|
vision.capture.name.prefix = Camera capture:
|
|
1615
1617
|
vision.capture.options.title = Video capture
|
|
1616
1618
|
vision.checkbox.tooltip = If checked, the vision model is active. It will be automatically activated upon image upload. You can deactivate it in real-time.
|
|
1617
|
-
assistant.store.menu.file.delete = Delete file
|
|
@@ -962,6 +962,7 @@ painter.btn.camera.capture = De la cámara
|
|
|
962
962
|
painter.btn.capture = Capturar imagen
|
|
963
963
|
painter.btn.clear = Limpiar
|
|
964
964
|
painter.btn.crop = Recortar
|
|
965
|
+
painter.btn.fit = Ajustar
|
|
965
966
|
painter.capture.manual.captured.success = Imagen capturada:
|
|
966
967
|
painter.capture.name.prefix = Dibujo de
|
|
967
968
|
painter.mode.erase = Borrar
|
|
@@ -961,6 +961,7 @@ painter.btn.camera.capture = De la caméra
|
|
|
961
961
|
painter.btn.capture = Capturer l'image
|
|
962
962
|
painter.btn.clear = Effacer
|
|
963
963
|
painter.btn.crop = Rogner
|
|
964
|
+
painter.btn.fit = Adapter
|
|
964
965
|
painter.capture.manual.captured.success = Image capturée:
|
|
965
966
|
painter.capture.name.prefix = Dessin de
|
|
966
967
|
painter.mode.erase = Effacer
|
|
@@ -961,6 +961,7 @@ painter.btn.camera.capture = Dalla fotocamera
|
|
|
961
961
|
painter.btn.capture = Cattura immagine
|
|
962
962
|
painter.btn.clear = Pulire
|
|
963
963
|
painter.btn.crop = Ritaglia
|
|
964
|
+
painter.btn.fit = Adatta
|
|
964
965
|
painter.capture.manual.captured.success = Immagine catturata:
|
|
965
966
|
painter.capture.name.prefix = Disegno da
|
|
966
967
|
painter.mode.erase = Cancellare
|
|
@@ -965,6 +965,7 @@ painter.btn.camera.capture = Z kamery
|
|
|
965
965
|
painter.btn.capture = Użyj obrazu
|
|
966
966
|
painter.btn.clear = Wyczyść
|
|
967
967
|
painter.btn.crop = Przytnij
|
|
968
|
+
painter.btn.fit = Dopasuj
|
|
968
969
|
painter.capture.manual.captured.success = Image captured:
|
|
969
970
|
painter.capture.name.prefix = Drawing from
|
|
970
971
|
painter.mode.erase = Gumka
|
|
@@ -1403,7 +1404,7 @@ settings.vision.capture.height = Wysokość przechwytywania (w pikselach)
|
|
|
1403
1404
|
settings.vision.capture.idx = Urządzenie Kamery
|
|
1404
1405
|
settings.vision.capture.idx.desc = Wybierz urządzenie kamery do przechwytywania wideo w czasie rzeczywistym
|
|
1405
1406
|
settings.vision.capture.quality = Jakość przechwytywania (%)
|
|
1406
|
-
settings.vision.capture.width = Szerokość przechwytywania (w pikselach)
|
|
1407
|
+
settings.vision.capture.width = Szerokość przechwytywania (w pikselach)
|
|
1407
1408
|
settings.zoom = Powiększenie okna outputu czatu
|
|
1408
1409
|
speech.enable = Mowa
|
|
1409
1410
|
speech.listening = Mów teraz...
|
|
@@ -961,6 +961,7 @@ painter.btn.camera.capture = З камери
|
|
|
961
961
|
painter.btn.capture = Захопити зображення
|
|
962
962
|
painter.btn.clear = Очистити
|
|
963
963
|
painter.btn.crop = Обрізати
|
|
964
|
+
painter.btn.fit = Підігнати
|
|
964
965
|
painter.capture.manual.captured.success = Зображення захоплено:
|
|
965
966
|
painter.capture.name.prefix = Малюнок з
|
|
966
967
|
painter.mode.erase = Стерти
|
|
@@ -961,6 +961,7 @@ painter.btn.camera.capture = 从相机
|
|
|
961
961
|
painter.btn.capture = 使用图像
|
|
962
962
|
painter.btn.clear = 清除
|
|
963
963
|
painter.btn.crop = 裁剪
|
|
964
|
+
painter.btn.fit = 适应
|
|
964
965
|
painter.capture.manual.captured.success = 图像已捕获:
|
|
965
966
|
painter.capture.name.prefix = 绘制自
|
|
966
967
|
painter.mode.erase = 擦除
|
|
@@ -81,10 +81,7 @@ class ApiGoogle:
|
|
|
81
81
|
filtered["location"] = os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
|
|
82
82
|
# filtered["http_options"] = gtypes.HttpOptions(api_version="v1")
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
self.client = genai.Client(**filtered)
|
|
86
|
-
self.last_client_args = filtered
|
|
87
|
-
return self.client
|
|
84
|
+
return genai.Client(**filtered)
|
|
88
85
|
|
|
89
86
|
def call(
|
|
90
87
|
self,
|
|
@@ -337,4 +334,16 @@ class ApiGoogle:
|
|
|
337
334
|
# self.client.close()
|
|
338
335
|
except Exception as e:
|
|
339
336
|
self.window.core.debug.log(e)
|
|
340
|
-
print("Error closing Google client:", e)
|
|
337
|
+
print("Error closing Google client:", e)
|
|
338
|
+
|
|
339
|
+
def safe_close(self):
|
|
340
|
+
"""Close client"""
|
|
341
|
+
if self.locked:
|
|
342
|
+
return
|
|
343
|
+
if self.client is not None:
|
|
344
|
+
try:
|
|
345
|
+
self.client.close()
|
|
346
|
+
self.client = None
|
|
347
|
+
except Exception as e:
|
|
348
|
+
self.window.core.debug.log(e)
|
|
349
|
+
print("Error closing client:", e)
|