pygpt-net 2.6.63__py3-none-any.whl → 2.6.65__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 +16 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +3 -1
- pygpt_net/controller/attachment/attachment.py +17 -8
- pygpt_net/controller/camera/camera.py +4 -4
- pygpt_net/controller/files/files.py +71 -2
- pygpt_net/controller/lang/custom.py +2 -2
- pygpt_net/controller/presets/editor.py +137 -22
- pygpt_net/controller/ui/mode.py +18 -3
- pygpt_net/core/agents/custom/__init__.py +18 -2
- pygpt_net/core/agents/custom/runner.py +2 -2
- pygpt_net/core/attachments/clipboard.py +146 -0
- pygpt_net/core/render/web/renderer.py +44 -11
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/presets/agent_openai_coder.json +15 -1
- pygpt_net/data/css/style.dark.css +12 -0
- pygpt_net/data/css/style.light.css +12 -0
- pygpt_net/data/icons/pin2.svg +1 -0
- pygpt_net/data/icons/pin3.svg +3 -0
- pygpt_net/data/icons/point.svg +1 -0
- pygpt_net/data/icons/target.svg +1 -0
- pygpt_net/data/js/app/runtime.js +11 -4
- pygpt_net/data/js/app/scroll.js +14 -0
- pygpt_net/data/js/app/ui.js +19 -2
- pygpt_net/data/js/app/user.js +22 -54
- pygpt_net/data/js/app.min.js +13 -14
- pygpt_net/data/locale/locale.de.ini +32 -0
- pygpt_net/data/locale/locale.en.ini +38 -2
- pygpt_net/data/locale/locale.es.ini +32 -0
- pygpt_net/data/locale/locale.fr.ini +32 -0
- pygpt_net/data/locale/locale.it.ini +32 -0
- pygpt_net/data/locale/locale.pl.ini +34 -2
- pygpt_net/data/locale/locale.uk.ini +32 -0
- pygpt_net/data/locale/locale.zh.ini +32 -0
- pygpt_net/icons.qrc +4 -0
- pygpt_net/icons_rc.py +274 -137
- pygpt_net/js_rc.py +8262 -8230
- pygpt_net/provider/agents/llama_index/planner_workflow.py +15 -3
- pygpt_net/provider/agents/llama_index/workflow/planner.py +69 -41
- pygpt_net/provider/agents/openai/agent_planner.py +57 -35
- pygpt_net/provider/agents/openai/evolve.py +0 -3
- pygpt_net/provider/api/google/__init__.py +9 -3
- pygpt_net/provider/api/google/image.py +11 -1
- pygpt_net/provider/api/google/music.py +375 -0
- pygpt_net/provider/core/config/patch.py +8 -0
- pygpt_net/ui/__init__.py +6 -1
- pygpt_net/ui/dialog/preset.py +9 -4
- pygpt_net/ui/layout/chat/attachments.py +18 -1
- pygpt_net/ui/layout/status.py +3 -3
- pygpt_net/ui/widget/element/status.py +55 -0
- pygpt_net/ui/widget/filesystem/explorer.py +116 -2
- pygpt_net/ui/widget/lists/context.py +26 -16
- pygpt_net/ui/widget/option/combo.py +149 -11
- pygpt_net/ui/widget/textarea/input.py +71 -17
- pygpt_net/ui/widget/textarea/web.py +1 -1
- pygpt_net/ui/widget/vision/camera.py +135 -12
- {pygpt_net-2.6.63.dist-info → pygpt_net-2.6.65.dist-info}/METADATA +18 -2
- {pygpt_net-2.6.63.dist-info → pygpt_net-2.6.65.dist-info}/RECORD +62 -55
- {pygpt_net-2.6.63.dist-info → pygpt_net-2.6.65.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.63.dist-info → pygpt_net-2.6.65.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.63.dist-info → pygpt_net-2.6.65.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.09.28 08:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from PySide6.QtCore import QObject, QEvent, Qt
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AttachmentDropHandler(QObject):
|
|
16
|
+
"""
|
|
17
|
+
Generic drag & drop handler for attaching files/images/urls/text.
|
|
18
|
+
|
|
19
|
+
Policies:
|
|
20
|
+
- SWALLOW_ALL: always consume the drop (e.g., attachments list).
|
|
21
|
+
- INPUT_MIX : for ChatInput; process attachments and:
|
|
22
|
+
* swallow image payloads (no text insert),
|
|
23
|
+
* allow default handling for non-image payloads so paths/text get inserted.
|
|
24
|
+
"""
|
|
25
|
+
SWALLOW_ALL = 0
|
|
26
|
+
INPUT_MIX = 1
|
|
27
|
+
|
|
28
|
+
def __init__(self, window, target_widget, policy=SWALLOW_ALL):
|
|
29
|
+
super().__init__(target_widget)
|
|
30
|
+
self.window = window
|
|
31
|
+
self._target = target_widget
|
|
32
|
+
self._policy = policy
|
|
33
|
+
|
|
34
|
+
# Accept drops on target and its viewport (important for QTextEdit/QAbstractScrollArea)
|
|
35
|
+
self._enable_drops(self._target)
|
|
36
|
+
vp = self._get_viewport(self._target)
|
|
37
|
+
if vp is not None:
|
|
38
|
+
self._enable_drops(vp)
|
|
39
|
+
|
|
40
|
+
# Install filters on both
|
|
41
|
+
self._target.installEventFilter(self)
|
|
42
|
+
if vp is not None:
|
|
43
|
+
vp.installEventFilter(self)
|
|
44
|
+
|
|
45
|
+
def _enable_drops(self, w):
|
|
46
|
+
try:
|
|
47
|
+
w.setAcceptDrops(True)
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def _get_viewport(self, w):
|
|
52
|
+
try:
|
|
53
|
+
vp = getattr(w, "viewport", None)
|
|
54
|
+
if callable(vp):
|
|
55
|
+
return vp()
|
|
56
|
+
return None
|
|
57
|
+
except Exception:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def _mime_supported(self, md) -> bool:
|
|
61
|
+
try:
|
|
62
|
+
if md is None:
|
|
63
|
+
return False
|
|
64
|
+
return md.hasUrls() or md.hasImage() or md.hasText()
|
|
65
|
+
except Exception:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def _process_drop(self, md):
|
|
69
|
+
"""
|
|
70
|
+
Route to ChatInput.handle_clipboard() to reuse existing attach pipeline.
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
chat_input = self.window.ui.nodes.get('input')
|
|
74
|
+
except Exception:
|
|
75
|
+
chat_input = None
|
|
76
|
+
|
|
77
|
+
if chat_input is not None and hasattr(chat_input, 'handle_clipboard'):
|
|
78
|
+
try:
|
|
79
|
+
chat_input.handle_clipboard(md)
|
|
80
|
+
return chat_input
|
|
81
|
+
except Exception as e:
|
|
82
|
+
try:
|
|
83
|
+
self.window.core.debug.log(e)
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def _allow_default_text_insert_for_non_image(self, md) -> bool:
|
|
89
|
+
try:
|
|
90
|
+
return not (md and md.hasImage())
|
|
91
|
+
except Exception:
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
def eventFilter(self, obj, event):
|
|
95
|
+
# Only handle events coming to the target or its viewport
|
|
96
|
+
if obj is not self._target and obj is not self._get_viewport(self._target):
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
et = event.type()
|
|
100
|
+
|
|
101
|
+
if et in (QEvent.DragEnter, QEvent.DragMove):
|
|
102
|
+
md = getattr(event, 'mimeData', lambda: None)()
|
|
103
|
+
if self._mime_supported(md):
|
|
104
|
+
try:
|
|
105
|
+
event.setDropAction(Qt.CopyAction)
|
|
106
|
+
event.acceptProposedAction()
|
|
107
|
+
except Exception:
|
|
108
|
+
event.accept()
|
|
109
|
+
return True
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
if et == QEvent.Drop:
|
|
113
|
+
md = getattr(event, 'mimeData', lambda: None)()
|
|
114
|
+
if not self._mime_supported(md):
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
chat_input = self._process_drop(md)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
event.setDropAction(Qt.CopyAction)
|
|
121
|
+
event.acceptProposedAction()
|
|
122
|
+
except Exception:
|
|
123
|
+
event.accept()
|
|
124
|
+
|
|
125
|
+
# Policy decision:
|
|
126
|
+
if self._policy == self.SWALLOW_ALL:
|
|
127
|
+
# Consume the event; nothing else should handle it.
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
if self._policy == self.INPUT_MIX:
|
|
131
|
+
# For non-image payloads we allow default to insert text/paths into input.
|
|
132
|
+
# To avoid duplicate attachments (insertFromMimeData calls handle_clipboard),
|
|
133
|
+
# set a one-shot guard flag.
|
|
134
|
+
if chat_input is not None and self._allow_default_text_insert_for_non_image(md):
|
|
135
|
+
try:
|
|
136
|
+
chat_input._skip_clipboard_on_next_insert = True
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
return False # let default drop insert text/paths
|
|
140
|
+
else:
|
|
141
|
+
return True # swallow images
|
|
142
|
+
|
|
143
|
+
# Default: swallow
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
return False
|
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.28 10:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -386,6 +386,11 @@ class Renderer(BaseRenderer):
|
|
|
386
386
|
except Exception:
|
|
387
387
|
pass
|
|
388
388
|
|
|
389
|
+
try:
|
|
390
|
+
self.get_output_node(meta).page().runJavaScript("if (typeof window.begin !== 'undefined') begin();")
|
|
391
|
+
except Exception:
|
|
392
|
+
pass
|
|
393
|
+
|
|
389
394
|
def end(self, meta: CtxMeta, ctx: CtxItem, stream: bool = False):
|
|
390
395
|
"""
|
|
391
396
|
Render end
|
|
@@ -402,6 +407,12 @@ class Renderer(BaseRenderer):
|
|
|
402
407
|
self.pids[pid].item = None
|
|
403
408
|
else:
|
|
404
409
|
self.reload()
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
self.get_output_node(meta).page().runJavaScript("if (typeof window.end !== 'undefined') end();")
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
405
416
|
self.pids[pid].clear()
|
|
406
417
|
self.auto_cleanup(meta)
|
|
407
418
|
|
|
@@ -1205,11 +1216,10 @@ class Renderer(BaseRenderer):
|
|
|
1205
1216
|
if preset.ai_name:
|
|
1206
1217
|
output_name = preset.ai_name
|
|
1207
1218
|
if preset.ai_avatar:
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
avatar_html = f"<img src=\"{self._file_prefix}{avatar_path}\" class=\"avatar\"> "
|
|
1219
|
+
# prefer thumbnail "thumb_<name>" when available
|
|
1220
|
+
avatar_fs = self._resolve_avatar_fs_path(preset.ai_avatar)
|
|
1221
|
+
if avatar_fs:
|
|
1222
|
+
avatar_html = f"<img src=\"{self._file_prefix}{avatar_fs}\" class=\"avatar\"> "
|
|
1213
1223
|
|
|
1214
1224
|
if not output_name and not avatar_html:
|
|
1215
1225
|
return ""
|
|
@@ -1951,6 +1961,30 @@ class Renderer(BaseRenderer):
|
|
|
1951
1961
|
pass
|
|
1952
1962
|
return None
|
|
1953
1963
|
|
|
1964
|
+
def _resolve_avatar_fs_path(self, basename: str) -> Optional[str]:
|
|
1965
|
+
"""
|
|
1966
|
+
Resolve avatar file system path preferring a local thumbnail named 'thumb_<basename>'.
|
|
1967
|
+
|
|
1968
|
+
Returns:
|
|
1969
|
+
- Absolute path to thumbnail when exists,
|
|
1970
|
+
- Otherwise absolute path to original when exists,
|
|
1971
|
+
- Otherwise None.
|
|
1972
|
+
"""
|
|
1973
|
+
if not basename:
|
|
1974
|
+
return None
|
|
1975
|
+
try:
|
|
1976
|
+
presets_dir = self.window.core.config.get_user_dir("presets")
|
|
1977
|
+
avatars_dir = os.path.join(presets_dir, "avatars")
|
|
1978
|
+
thumb = os.path.join(avatars_dir, f"thumb_{basename}")
|
|
1979
|
+
original = os.path.join(avatars_dir, basename)
|
|
1980
|
+
if os.path.exists(thumb):
|
|
1981
|
+
return thumb
|
|
1982
|
+
if os.path.exists(original):
|
|
1983
|
+
return original
|
|
1984
|
+
except Exception:
|
|
1985
|
+
pass
|
|
1986
|
+
return None
|
|
1987
|
+
|
|
1954
1988
|
def _output_identity(self, ctx: CtxItem) -> Tuple[str, Optional[str], bool]:
|
|
1955
1989
|
"""
|
|
1956
1990
|
Resolve output identity (name, avatar file:// path) based on preset or ctx-provided agent name.
|
|
@@ -1988,11 +2022,10 @@ class Renderer(BaseRenderer):
|
|
|
1988
2022
|
name = preset.ai_name or default_name
|
|
1989
2023
|
avatar = None
|
|
1990
2024
|
if preset.ai_avatar:
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
avatar = f"{self._file_prefix}{avatar_path}"
|
|
2025
|
+
# prefer thumbnail URL if available
|
|
2026
|
+
avatar_fs = self._resolve_avatar_fs_path(preset.ai_avatar)
|
|
2027
|
+
if avatar_fs:
|
|
2028
|
+
avatar = f"{self._file_prefix}{avatar_fs}"
|
|
1996
2029
|
return name, avatar, True
|
|
1997
2030
|
|
|
1998
2031
|
def _build_render_block(
|
|
@@ -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.65",
|
|
4
|
+
"app.version": "2.6.65",
|
|
5
|
+
"updated_at": "2025-09-28T00: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-09-
|
|
3
|
+
"version": "2.6.65",
|
|
4
|
+
"app.version": "2.6.65",
|
|
5
|
+
"updated_at": "2025-09-28T00:00:00"
|
|
6
6
|
},
|
|
7
7
|
"items": {
|
|
8
8
|
"SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
|
|
@@ -31,7 +31,21 @@
|
|
|
31
31
|
"enabled": true,
|
|
32
32
|
"description": "",
|
|
33
33
|
"remote_tools": "",
|
|
34
|
-
"extra": {
|
|
34
|
+
"extra": {
|
|
35
|
+
"openai_agent_feedback": {
|
|
36
|
+
"base": {
|
|
37
|
+
"prompt": "You are senior programmer and expert in coding. Use markdown for code blocks. If there is any feedback provided, use it to improve the code.",
|
|
38
|
+
"allow_local_tools": false,
|
|
39
|
+
"allow_remote_tools": false
|
|
40
|
+
},
|
|
41
|
+
"feedback": {
|
|
42
|
+
"model": "o3-mini-low",
|
|
43
|
+
"prompt": "You evaluate a code and decide if it's correct. If it's not correct, you provide feedback on what needs to be fixed and improved. Never give it a pass on the first try. After 5 attempts, you can give it a pass if the code is good enough. You can use tools for checking the code, running tests, etc.",
|
|
44
|
+
"allow_local_tools": false,
|
|
45
|
+
"allow_remote_tools": false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
35
49
|
"__meta__": {
|
|
36
50
|
"version": "2.5.81",
|
|
37
51
|
"app.version": "2.5.81",
|
|
@@ -29,6 +29,13 @@ QListView::item {{
|
|
|
29
29
|
padding: 4px;
|
|
30
30
|
min-height: 20px;
|
|
31
31
|
}}
|
|
32
|
+
|
|
33
|
+
/* Subtle hover for list/tree items (non-selected) */
|
|
34
|
+
QListView::item:hover:!selected,
|
|
35
|
+
QTreeView::item:hover:!selected {{
|
|
36
|
+
background-color: rgba(255, 255, 255, 5);
|
|
37
|
+
}}
|
|
38
|
+
|
|
32
39
|
QListView::item:selected,
|
|
33
40
|
QTreeView::item:selected {{
|
|
34
41
|
color: #ffffff;
|
|
@@ -162,4 +169,9 @@ NodeEditor {{
|
|
|
162
169
|
|
|
163
170
|
qproperty-edgeColor: #c0c0c0;
|
|
164
171
|
qproperty-edgeSelectedColor: #ff8a5c;
|
|
172
|
+
}}
|
|
173
|
+
|
|
174
|
+
/* Status Bar */
|
|
175
|
+
#StatusBarTimer {{
|
|
176
|
+
color: #999 !important;
|
|
165
177
|
}}
|
|
@@ -82,6 +82,13 @@ QListView::item {{
|
|
|
82
82
|
padding: 4px;
|
|
83
83
|
min-height: 20px;
|
|
84
84
|
}}
|
|
85
|
+
|
|
86
|
+
/* Subtle hover for list/tree items (non-selected) */
|
|
87
|
+
QListView::item:hover:!selected,
|
|
88
|
+
QTreeView::item:hover:!selected {{
|
|
89
|
+
background-color: #f6f8fb;
|
|
90
|
+
}}
|
|
91
|
+
|
|
85
92
|
QCalendarWidget QWidget {{
|
|
86
93
|
background: #fff;
|
|
87
94
|
}}
|
|
@@ -278,4 +285,9 @@ NodeEditor {{
|
|
|
278
285
|
|
|
279
286
|
qproperty-edgeColor: #c0c0c0;
|
|
280
287
|
qproperty-edgeSelectedColor: #ff8a5c;
|
|
288
|
+
}}
|
|
289
|
+
|
|
290
|
+
/* Status Bar */
|
|
291
|
+
#StatusBarTimer {{
|
|
292
|
+
color: #5d5d5d !important;
|
|
281
293
|
}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#686868"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24px" height="24px" fill="#686868" version="1.1" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="m621.42-355.15v113.14l-56.569 56.569-141.42-141.42-169.71 169.71h-56.569v-56.569l169.71-169.71-141.42-141.42 56.569-56.569h113.14l197.99-197.99-28.284-28.284 56.569-56.569 282.84 282.84-56.569 56.569-28.284-28.284zm-258.8-145.66 178.19 178.19v-65.054l222.03-222.03-113.14-113.14-222.03 222.03z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#686868"><path d="M480-400q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm-40-240v-200h80v200h-80Zm0 520v-200h80v200h-80Zm200-320v-80h200v80H640Zm-520 0v-80h200v80H120Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#686868"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z"/></svg>
|
pygpt_net/data/js/app/runtime.js
CHANGED
|
@@ -197,6 +197,7 @@ class Runtime {
|
|
|
197
197
|
api_appendNode = (payload) => {
|
|
198
198
|
this.resetStreamState('appendNode');
|
|
199
199
|
this.data.append(payload);
|
|
200
|
+
this.scrollMgr.scheduleScroll();
|
|
200
201
|
};
|
|
201
202
|
|
|
202
203
|
api_replaceNodes = (payload) => {
|
|
@@ -267,10 +268,7 @@ class Runtime {
|
|
|
267
268
|
api_updateToolOutput = (c) => this.toolOutput.update(c);
|
|
268
269
|
api_clearToolOutput = () => this.toolOutput.clear();
|
|
269
270
|
api_beginToolOutput = () => this.toolOutput.begin();
|
|
270
|
-
api_endToolOutput = () =>
|
|
271
|
-
this.toolOutput.end();
|
|
272
|
-
this.scrollMgr.scheduleScroll();
|
|
273
|
-
}
|
|
271
|
+
api_endToolOutput = () => this.toolOutput.end();
|
|
274
272
|
api_enableToolOutput = () => this.toolOutput.enable();
|
|
275
273
|
api_disableToolOutput = () => this.toolOutput.disable();
|
|
276
274
|
api_toggleToolOutput = (id) => this.toolOutput.toggle(id);
|
|
@@ -377,6 +375,12 @@ class Runtime {
|
|
|
377
375
|
api_showTips = () => this.tips.show();
|
|
378
376
|
api_hideTips = () => this.tips.hide();
|
|
379
377
|
|
|
378
|
+
// API: begin/end.
|
|
379
|
+
api_begin = () => {};
|
|
380
|
+
api_end = () => {
|
|
381
|
+
this.scrollMgr.forceScrollToBottomImmediateAtEnd();
|
|
382
|
+
}
|
|
383
|
+
|
|
380
384
|
// API: custom markup rules control.
|
|
381
385
|
api_getCustomMarkupRules = () => this.customMarkup.getRules();
|
|
382
386
|
api_setCustomMarkupRules = (rules) => {
|
|
@@ -481,6 +485,9 @@ window.appendStreamTyped = (type, name, chunk) => runtime.api_onChunk(name, chun
|
|
|
481
485
|
window.nextStream = () => runtime.api_nextStream();
|
|
482
486
|
window.clearStream = () => runtime.api_clearStream();
|
|
483
487
|
|
|
488
|
+
window.begin = () => runtime.api_begin();
|
|
489
|
+
window.end = () => runtime.api_end();
|
|
490
|
+
|
|
484
491
|
window.appendNode = (payload) => runtime.api_appendNode(payload);
|
|
485
492
|
window.replaceNodes = (payload) => runtime.api_replaceNodes(payload);
|
|
486
493
|
window.appendToInput = (html) => runtime.api_appendToInput(html);
|
pygpt_net/data/js/app/scroll.js
CHANGED
|
@@ -58,6 +58,20 @@ class ScrollManager {
|
|
|
58
58
|
this.prevScroll = el.scrollHeight;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// Jump to bottom immediately (no smooth behavior).
|
|
62
|
+
forceScrollToBottomImmediateAtEnd() {
|
|
63
|
+
if (this.userInteracted === true || !this.isNearBottom(200)) return;
|
|
64
|
+
const el = Utils.SE;
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
el.scrollTo({
|
|
67
|
+
top: el.scrollHeight,
|
|
68
|
+
behavior: 'instant'
|
|
69
|
+
});
|
|
70
|
+
this.lastScrollTop = el.scrollTop;
|
|
71
|
+
this.prevScroll = el.scrollHeight;
|
|
72
|
+
}, 100);
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
// Scroll window to bottom based on auto-follow and margins.
|
|
62
76
|
scrollToBottom(live = false, force = false) {
|
|
63
77
|
const el = Utils.SE;
|
pygpt_net/data/js/app/ui.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// app/ui.js
|
|
2
|
+
|
|
1
3
|
// ==========================================================================
|
|
2
4
|
// UI manager
|
|
3
5
|
// ==========================================================================
|
|
@@ -30,10 +32,25 @@ class UIManager {
|
|
|
30
32
|
'#_loader_.hidden { display: none !important; visibility: hidden !important; }',
|
|
31
33
|
'#_loader_.visible { display: block; visibility: visible; }',
|
|
32
34
|
|
|
33
|
-
/* User message collapse (uc-*)
|
|
35
|
+
/* User message collapse (uc-*)
|
|
36
|
+
Collapsed content now fades out towards the bottom using a CSS mask.
|
|
37
|
+
This removes the need for an inline "..." overlay and works in light/dark themes. */
|
|
34
38
|
'.msg-box.msg-user .msg { position: relative; }',
|
|
35
39
|
'.msg-box.msg-user .msg > .uc-content { display: block; overflow: visible; }',
|
|
36
|
-
'.msg-box.msg-user .msg > .uc-content.uc-collapsed {
|
|
40
|
+
'.msg-box.msg-user .msg > .uc-content.uc-collapsed {',
|
|
41
|
+
' max-height: var(--user-msg-collapse-max-h, 1000px);',
|
|
42
|
+
' overflow: hidden;',
|
|
43
|
+
' -webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,1) calc(100% - var(--uc-fade-height, 64px)), rgba(0,0,0,0) 100%);',
|
|
44
|
+
' mask-image: linear-gradient(to bottom, rgba(0,0,0,1) calc(100% - var(--uc-fade-height, 64px)), rgba(0,0,0,0) 100%);',
|
|
45
|
+
' -webkit-mask-size: 100% 100%;',
|
|
46
|
+
' mask-size: 100% 100%;',
|
|
47
|
+
' -webkit-mask-repeat: no-repeat;',
|
|
48
|
+
' mask-repeat: no-repeat;',
|
|
49
|
+
'}',
|
|
50
|
+
'.msg-box.msg-user .msg > .uc-content.uc-expanded {',
|
|
51
|
+
' -webkit-mask-image: none;',
|
|
52
|
+
' mask-image: none;',
|
|
53
|
+
'}',
|
|
37
54
|
'.msg-box.msg-user .msg > .uc-toggle { display: none; margin-top: 8px; text-align: center; cursor: pointer; user-select: none; }',
|
|
38
55
|
'.msg-box.msg-user .msg > .uc-toggle.visible { display: block; }',
|
|
39
56
|
|
pygpt_net/data/js/app/user.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// app/user.js
|
|
2
|
+
|
|
1
3
|
// ==========================================================================
|
|
2
4
|
// User collapse manager
|
|
3
5
|
// ==========================================================================
|
|
@@ -12,8 +14,7 @@ class UserCollapseManager {
|
|
|
12
14
|
// Track processed .msg elements to allow cheap remeasure on resize if needed.
|
|
13
15
|
this._processed = new Set();
|
|
14
16
|
|
|
15
|
-
// Visual indicator
|
|
16
|
-
this.ellipsisText = ' [...]';
|
|
17
|
+
// Visual indicator is now purely CSS-based (mask fade) – no inline "..." text injected anymore.
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
// Icon paths for the collapse/expand buttons.
|
|
@@ -158,60 +159,27 @@ class UserCollapseManager {
|
|
|
158
159
|
};
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
//
|
|
162
|
+
// Previously created an inline "..." indicator; now acts as a no-op and cleans legacy nodes if present.
|
|
162
163
|
_ensureEllipsisEl(msg, contentEl) {
|
|
163
164
|
const content = contentEl || (msg && msg.querySelector('.uc-content'));
|
|
164
165
|
if (!content) return null;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!dot) {
|
|
173
|
-
dot = document.createElement('span');
|
|
174
|
-
dot.className = 'uc-ellipsis';
|
|
175
|
-
dot.textContent = this.ellipsisText;
|
|
176
|
-
// Inline, theme-agnostic styles; kept minimal and non-interactive.
|
|
177
|
-
dot.style.position = 'absolute';
|
|
178
|
-
dot.style.right = '0';
|
|
179
|
-
dot.style.bottom = '0';
|
|
180
|
-
dot.style.paddingLeft = '6px';
|
|
181
|
-
dot.style.pointerEvents = 'none';
|
|
182
|
-
dot.style.zIndex = '1';
|
|
183
|
-
dot.style.fontWeight = '500';
|
|
184
|
-
dot.style.opacity = '0.75';
|
|
185
|
-
// Do not include in copy-to-clipboard.
|
|
186
|
-
dot.setAttribute('aria-hidden', 'true');
|
|
187
|
-
dot.setAttribute('data-copy-ignore', '1');
|
|
188
|
-
|
|
189
|
-
content.appendChild(dot);
|
|
190
|
-
}
|
|
191
|
-
return dot;
|
|
166
|
+
try {
|
|
167
|
+
const legacy = content.querySelector('.uc-ellipsis');
|
|
168
|
+
if (legacy && legacy.parentNode) {
|
|
169
|
+
legacy.parentNode.removeChild(legacy);
|
|
170
|
+
}
|
|
171
|
+
} catch (_) {}
|
|
172
|
+
return null;
|
|
192
173
|
}
|
|
193
174
|
|
|
194
|
-
//
|
|
175
|
+
// No visual node is needed anymore; fade is applied via CSS on .uc-collapsed.
|
|
195
176
|
_showEllipsis(msg, contentEl) {
|
|
196
|
-
|
|
197
|
-
if (dot) dot.style.display = 'inline';
|
|
177
|
+
this._ensureEllipsisEl(msg, contentEl);
|
|
198
178
|
}
|
|
199
|
-
|
|
200
|
-
//
|
|
179
|
+
|
|
180
|
+
// No visual node is needed anymore; keep DOM lean and clean any legacy nodes.
|
|
201
181
|
_hideEllipsis(msg) {
|
|
202
|
-
|
|
203
|
-
if (!content) return;
|
|
204
|
-
const dot = content.querySelector('.uc-ellipsis');
|
|
205
|
-
if (dot && dot.parentNode) {
|
|
206
|
-
// Remove the indicator to avoid accidental copy/select and keep DOM lean.
|
|
207
|
-
dot.parentNode.removeChild(dot);
|
|
208
|
-
}
|
|
209
|
-
// Drop positioning context when no indicator is present (keep styles minimal).
|
|
210
|
-
try {
|
|
211
|
-
if (content && content.style && content.querySelector('.uc-ellipsis') == null) {
|
|
212
|
-
content.style.position = '';
|
|
213
|
-
}
|
|
214
|
-
} catch (_) {}
|
|
182
|
+
this._ensureEllipsisEl(msg, null);
|
|
215
183
|
}
|
|
216
184
|
|
|
217
185
|
// Apply collapse to all user messages under root.
|
|
@@ -247,7 +215,7 @@ class UserCollapseManager {
|
|
|
247
215
|
c.classList.remove('uc-expanded'); // No class => fully expanded by default CSS.
|
|
248
216
|
msg.dataset.ucState = 'expanded';
|
|
249
217
|
|
|
250
|
-
// Hide ellipsis in disabled mode.
|
|
218
|
+
// Hide ellipsis in disabled mode (no-op, CSS fade not applied without .uc-collapsed).
|
|
251
219
|
this._hideEllipsis(msg);
|
|
252
220
|
|
|
253
221
|
// Hide toggle in disabled mode to avoid user interaction.
|
|
@@ -279,10 +247,10 @@ class UserCollapseManager {
|
|
|
279
247
|
|
|
280
248
|
if (expand) {
|
|
281
249
|
c.classList.add('uc-expanded');
|
|
282
|
-
this._hideEllipsis(msg); // Expanded =>
|
|
250
|
+
this._hideEllipsis(msg); // Expanded => nothing to show (CSS fade applies only to .uc-collapsed)
|
|
283
251
|
} else {
|
|
284
252
|
c.classList.add('uc-collapsed');
|
|
285
|
-
this._showEllipsis(msg, c); // Collapsed =>
|
|
253
|
+
this._showEllipsis(msg, c); // Collapsed => CSS fade handled by stylesheet
|
|
286
254
|
}
|
|
287
255
|
|
|
288
256
|
if (t) {
|
|
@@ -300,7 +268,7 @@ class UserCollapseManager {
|
|
|
300
268
|
t.title = expand ? labels.collapse : labels.expand;
|
|
301
269
|
}
|
|
302
270
|
} else {
|
|
303
|
-
// Short content – ensure fully expanded and hide toggle + ellipsis.
|
|
271
|
+
// Short content – ensure fully expanded and hide toggle + (legacy) ellipsis cleanup.
|
|
304
272
|
c.classList.remove('uc-collapsed');
|
|
305
273
|
c.classList.remove('uc-expanded');
|
|
306
274
|
msg.dataset.ucState = 'expanded';
|
|
@@ -331,7 +299,7 @@ class UserCollapseManager {
|
|
|
331
299
|
|
|
332
300
|
const isCollapsed = c.classList.contains('uc-collapsed');
|
|
333
301
|
if (isCollapsed) {
|
|
334
|
-
// Expand – leave scroll as-is
|
|
302
|
+
// Expand – leave scroll as-is.
|
|
335
303
|
c.classList.remove('uc-collapsed');
|
|
336
304
|
c.classList.add('uc-expanded');
|
|
337
305
|
msg.dataset.ucState = 'expanded';
|
|
@@ -346,7 +314,7 @@ class UserCollapseManager {
|
|
|
346
314
|
}
|
|
347
315
|
}
|
|
348
316
|
} else {
|
|
349
|
-
// Collapse – apply classes,
|
|
317
|
+
// Collapse – apply classes, then bring toggle into view (scroll up if needed).
|
|
350
318
|
c.classList.remove('uc-expanded');
|
|
351
319
|
c.classList.add('uc-collapsed');
|
|
352
320
|
msg.dataset.ucState = 'collapsed';
|