pygpt-net 2.6.60__py3-none-any.whl → 2.6.61__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 +115 -6
- pygpt_net/controller/chat/input.py +4 -1
- pygpt_net/controller/presets/presets.py +121 -6
- pygpt_net/controller/settings/editor.py +0 -15
- pygpt_net/controller/theme/markdown.py +2 -5
- pygpt_net/controller/ui/ui.py +4 -7
- pygpt_net/core/agents/custom/__init__.py +7 -1
- pygpt_net/core/agents/custom/llama_index/factory.py +17 -6
- pygpt_net/core/agents/custom/llama_index/runner.py +35 -2
- pygpt_net/core/agents/custom/llama_index/utils.py +12 -1
- pygpt_net/core/agents/custom/router.py +45 -6
- pygpt_net/core/agents/custom/runner.py +2 -1
- pygpt_net/core/agents/custom/schema.py +3 -1
- pygpt_net/core/agents/custom/utils.py +13 -1
- pygpt_net/core/db/viewer.py +11 -5
- pygpt_net/core/node_editor/graph.py +18 -9
- pygpt_net/core/node_editor/models.py +9 -2
- pygpt_net/core/node_editor/types.py +3 -1
- pygpt_net/core/presets/presets.py +216 -29
- pygpt_net/core/render/markdown/parser.py +0 -2
- pygpt_net/data/config/config.json +5 -6
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +2 -38
- pygpt_net/data/locale/locale.de.ini +64 -1
- pygpt_net/data/locale/locale.en.ini +62 -3
- pygpt_net/data/locale/locale.es.ini +64 -1
- pygpt_net/data/locale/locale.fr.ini +64 -1
- pygpt_net/data/locale/locale.it.ini +64 -1
- pygpt_net/data/locale/locale.pl.ini +65 -2
- pygpt_net/data/locale/locale.uk.ini +64 -1
- pygpt_net/data/locale/locale.zh.ini +64 -1
- pygpt_net/data/locale/plugin.cmd_system.en.ini +62 -66
- pygpt_net/provider/agents/llama_index/flow_from_schema.py +2 -2
- pygpt_net/provider/core/config/patch.py +10 -1
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -6
- pygpt_net/tools/agent_builder/tool.py +42 -26
- pygpt_net/tools/agent_builder/ui/dialogs.py +60 -11
- pygpt_net/ui/__init__.py +2 -4
- pygpt_net/ui/dialog/about.py +58 -38
- pygpt_net/ui/dialog/db.py +142 -3
- pygpt_net/ui/dialog/preset.py +47 -8
- pygpt_net/ui/layout/toolbox/presets.py +52 -16
- pygpt_net/ui/widget/dialog/db.py +0 -0
- pygpt_net/ui/widget/lists/preset.py +644 -60
- pygpt_net/ui/widget/node_editor/command.py +10 -10
- pygpt_net/ui/widget/node_editor/config.py +157 -0
- pygpt_net/ui/widget/node_editor/editor.py +183 -151
- pygpt_net/ui/widget/node_editor/item.py +12 -11
- pygpt_net/ui/widget/node_editor/node.py +267 -12
- pygpt_net/ui/widget/node_editor/view.py +180 -63
- pygpt_net/ui/widget/tabs/output.py +1 -1
- pygpt_net/ui/widget/textarea/input.py +2 -2
- pygpt_net/utils.py +114 -2
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/METADATA +11 -94
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/RECORD +59 -58
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.60.dist-info → pygpt_net-2.6.61.dist-info}/entry_points.txt +0 -0
|
@@ -6,15 +6,17 @@
|
|
|
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.26 03:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
from typing import Optional, Tuple
|
|
14
14
|
|
|
15
|
-
from PySide6.QtCore import Qt, QPointF, QRectF, QObject, Signal
|
|
16
|
-
from PySide6.QtGui import QColor, QPainter, QPen, QTransform
|
|
17
|
-
from PySide6.QtWidgets import QWidget, QGraphicsView, QGraphicsScene
|
|
15
|
+
from PySide6.QtCore import Qt, QPointF, QRectF, QObject, Signal, QSize, QEvent
|
|
16
|
+
from PySide6.QtGui import QColor, QPainter, QPen, QTransform, QIcon
|
|
17
|
+
from PySide6.QtWidgets import QWidget, QGraphicsView, QGraphicsScene, QPushButton, QHBoxLayout, QLabel
|
|
18
|
+
|
|
19
|
+
from .config import EditorConfig
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
# ------------------------ Graphics View / Scene ------------------------
|
|
@@ -26,14 +28,7 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
26
28
|
- Ctrl + Mouse Wheel zooming
|
|
27
29
|
- Middle Mouse Button panning
|
|
28
30
|
- Rubber band selection
|
|
29
|
-
|
|
30
|
-
Notes:
|
|
31
|
-
- Space-panning is intentionally disabled to not conflict with typing in editors.
|
|
32
|
-
- All keyboard shortcuts (e.g., Delete) are handled at the NodeEditor level.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
scene: Shared QGraphicsScene instance for the editor.
|
|
36
|
-
parent: Optional parent widget.
|
|
31
|
+
- Optional left-button panning only when global grab mode is enabled
|
|
37
32
|
"""
|
|
38
33
|
def __init__(self, scene: QGraphicsScene, parent: Optional[QWidget] = None):
|
|
39
34
|
super().__init__(scene, parent)
|
|
@@ -53,17 +48,10 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
53
48
|
|
|
54
49
|
self._panning = False
|
|
55
50
|
self._last_pan_pos = None
|
|
51
|
+
self._global_grab_mode = False # when True, left button pans regardless of items
|
|
56
52
|
|
|
57
53
|
def drawBackground(self, painter: QPainter, rect: QRectF):
|
|
58
|
-
"""Draw the checker grid in the background.
|
|
59
|
-
|
|
60
|
-
The grid spacing is fixed (20 px). Colors are read from the owning NodeEditor
|
|
61
|
-
instance if available, which allows dynamic theming.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
painter: Active QPainter provided by Qt.
|
|
65
|
-
rect: The exposed background rect to be filled.
|
|
66
|
-
"""
|
|
54
|
+
"""Draw the checker grid in the background."""
|
|
67
55
|
parent_editor = self.parent() # NodeEditor
|
|
68
56
|
color_back = getattr(parent_editor, "_grid_back_color", QColor(35, 35, 38))
|
|
69
57
|
color_pen = getattr(parent_editor, "_grid_pen_color", QColor(55, 55, 60))
|
|
@@ -83,15 +71,23 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
83
71
|
painter.drawLine(rect.left(), y, rect.right(), y)
|
|
84
72
|
y += grid
|
|
85
73
|
|
|
86
|
-
def
|
|
87
|
-
"""
|
|
74
|
+
def enterEvent(self, e: QEvent):
|
|
75
|
+
"""Ensure cursor reflects grab mode when entering the view."""
|
|
76
|
+
if self._global_grab_mode and not self._panning:
|
|
77
|
+
self.viewport().setCursor(Qt.OpenHandCursor)
|
|
78
|
+
else:
|
|
79
|
+
self.viewport().setCursor(Qt.ArrowCursor)
|
|
80
|
+
super().enterEvent(e)
|
|
81
|
+
|
|
82
|
+
def leaveEvent(self, e: QEvent):
|
|
83
|
+
"""Restore cursor on leave."""
|
|
84
|
+
self.viewport().setCursor(Qt.ArrowCursor)
|
|
85
|
+
super().leaveEvent(e)
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
"""
|
|
87
|
+
def keyPressEvent(self, e):
|
|
91
88
|
super().keyPressEvent(e)
|
|
92
89
|
|
|
93
90
|
def keyReleaseEvent(self, e):
|
|
94
|
-
"""Pass-through for key release."""
|
|
95
91
|
super().keyReleaseEvent(e)
|
|
96
92
|
|
|
97
93
|
def wheelEvent(self, e):
|
|
@@ -102,14 +98,47 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
102
98
|
return
|
|
103
99
|
super().wheelEvent(e)
|
|
104
100
|
|
|
101
|
+
def _begin_pan(self, e):
|
|
102
|
+
"""Start panning from current mouse event position."""
|
|
103
|
+
self._panning = True
|
|
104
|
+
self._last_pan_pos = e.position()
|
|
105
|
+
# Use 'grab' during drag
|
|
106
|
+
self.viewport().setCursor(Qt.ClosedHandCursor)
|
|
107
|
+
e.accept()
|
|
108
|
+
|
|
109
|
+
def _end_pan(self, e):
|
|
110
|
+
"""Stop panning and restore appropriate cursor."""
|
|
111
|
+
self._panning = False
|
|
112
|
+
if self._global_grab_mode:
|
|
113
|
+
self.viewport().setCursor(Qt.OpenHandCursor)
|
|
114
|
+
else:
|
|
115
|
+
self.viewport().setCursor(Qt.ArrowCursor)
|
|
116
|
+
e.accept()
|
|
117
|
+
|
|
118
|
+
def _clicked_on_empty(self, e) -> bool:
|
|
119
|
+
"""Return True if the click is on empty scene space (no items)."""
|
|
120
|
+
try:
|
|
121
|
+
item = self.itemAt(int(e.position().x()), int(e.position().y()))
|
|
122
|
+
return item is None
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|
|
125
|
+
|
|
105
126
|
def mousePressEvent(self, e):
|
|
106
|
-
"""
|
|
127
|
+
"""Panning: MMB always; LMB only in global grab mode. Also clear selection on empty click."""
|
|
107
128
|
if e.button() == Qt.MiddleButton:
|
|
108
|
-
self.
|
|
109
|
-
self._last_pan_pos = e.position()
|
|
110
|
-
self.setCursor(Qt.ClosedHandCursor)
|
|
111
|
-
e.accept()
|
|
129
|
+
self._begin_pan(e)
|
|
112
130
|
return
|
|
131
|
+
|
|
132
|
+
if e.button() == Qt.LeftButton:
|
|
133
|
+
if self._global_grab_mode:
|
|
134
|
+
# Global grab enabled -> pan on LMB anywhere
|
|
135
|
+
self._begin_pan(e)
|
|
136
|
+
return
|
|
137
|
+
else:
|
|
138
|
+
# No global grab: clicking empty clears selection
|
|
139
|
+
if self._clicked_on_empty(e) and self.scene():
|
|
140
|
+
self.scene().clearSelection()
|
|
141
|
+
|
|
113
142
|
super().mousePressEvent(e)
|
|
114
143
|
|
|
115
144
|
def mouseMoveEvent(self, e):
|
|
@@ -124,11 +153,9 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
124
153
|
super().mouseMoveEvent(e)
|
|
125
154
|
|
|
126
155
|
def mouseReleaseEvent(self, e):
|
|
127
|
-
"""Stop panning on Middle Mouse Button release; otherwise defer."""
|
|
128
|
-
if self._panning and e.button()
|
|
129
|
-
self.
|
|
130
|
-
self.setCursor(Qt.ArrowCursor)
|
|
131
|
-
e.accept()
|
|
156
|
+
"""Stop panning on Middle or Left Mouse Button release; otherwise defer."""
|
|
157
|
+
if self._panning and e.button() in (Qt.MiddleButton, Qt.LeftButton):
|
|
158
|
+
self._end_pan(e)
|
|
132
159
|
return
|
|
133
160
|
super().mouseReleaseEvent(e)
|
|
134
161
|
|
|
@@ -141,15 +168,7 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
141
168
|
self._apply_zoom(1.0 / self._zoom_step)
|
|
142
169
|
|
|
143
170
|
def _apply_zoom(self, factor: float):
|
|
144
|
-
"""Apply zoom scaling factor within configured bounds.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
factor: Multiplicative factor to apply to the current zoom.
|
|
148
|
-
|
|
149
|
-
Notes:
|
|
150
|
-
The method clamps the result to [_min_zoom, _max_zoom] to prevent
|
|
151
|
-
excessive zooming.
|
|
152
|
-
"""
|
|
171
|
+
"""Apply zoom scaling factor within configured bounds."""
|
|
153
172
|
new_zoom = self._zoom * factor
|
|
154
173
|
if not (self._min_zoom <= new_zoom <= self._max_zoom):
|
|
155
174
|
return
|
|
@@ -169,7 +188,6 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
169
188
|
if keep_center and self.viewport() is not None and self.viewport().rect().isValid():
|
|
170
189
|
center_scene = self.mapToScene(self.viewport().rect().center())
|
|
171
190
|
|
|
172
|
-
# Reset and apply new transform to avoid cumulative floating errors
|
|
173
191
|
self.resetTransform()
|
|
174
192
|
self._zoom = 1.0
|
|
175
193
|
if abs(z - 1.0) > 1e-9:
|
|
@@ -180,25 +198,21 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
180
198
|
self.centerOn(center_scene)
|
|
181
199
|
|
|
182
200
|
def get_scroll_values(self) -> Tuple[int, int]:
|
|
183
|
-
"""Return (horizontal, vertical) scrollbar values."""
|
|
184
201
|
h = self.horizontalScrollBar().value() if self.horizontalScrollBar() else 0
|
|
185
202
|
v = self.verticalScrollBar().value() if self.verticalScrollBar() else 0
|
|
186
203
|
return int(h), int(v)
|
|
187
204
|
|
|
188
205
|
def set_scroll_values(self, h: int, v: int):
|
|
189
|
-
"""Set horizontal and vertical scrollbar values."""
|
|
190
206
|
if self.horizontalScrollBar():
|
|
191
207
|
self.horizontalScrollBar().setValue(int(h))
|
|
192
208
|
if self.verticalScrollBar():
|
|
193
209
|
self.verticalScrollBar().setValue(int(v))
|
|
194
210
|
|
|
195
211
|
def view_state(self) -> dict:
|
|
196
|
-
"""Return a serializable view state: zoom and scrollbars."""
|
|
197
212
|
h, v = self.get_scroll_values()
|
|
198
213
|
return {"zoom": float(self._zoom), "h": h, "v": v}
|
|
199
214
|
|
|
200
215
|
def set_view_state(self, state: dict):
|
|
201
|
-
"""Apply a view state previously produced by view_state()."""
|
|
202
216
|
if not isinstance(state, dict):
|
|
203
217
|
return
|
|
204
218
|
z = state.get("zoom") or state.get("scale")
|
|
@@ -213,35 +227,138 @@ class NodeGraphicsView(QGraphicsView):
|
|
|
213
227
|
if h is not None:
|
|
214
228
|
self.set_scroll_values(int(h), int(v if v is not None else 0))
|
|
215
229
|
elif v is not None:
|
|
216
|
-
# set vertical if only v present
|
|
217
230
|
self.set_scroll_values(self.get_scroll_values()[0], int(v))
|
|
218
231
|
except Exception:
|
|
219
232
|
pass
|
|
220
233
|
|
|
234
|
+
def set_global_grab_mode(self, enabled: bool):
|
|
235
|
+
"""Enable/disable global grab mode (left click pans anywhere)."""
|
|
236
|
+
self._global_grab_mode = bool(enabled)
|
|
237
|
+
if self._global_grab_mode:
|
|
238
|
+
self.viewport().setCursor(Qt.OpenHandCursor)
|
|
239
|
+
else:
|
|
240
|
+
if not self._panning:
|
|
241
|
+
self.viewport().setCursor(Qt.ArrowCursor)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class NodeViewOverlayControls(QWidget):
|
|
245
|
+
"""Small overlay with three buttons (Grab toggle, Zoom Out, Zoom In) anchored top-right."""
|
|
246
|
+
|
|
247
|
+
grabToggled = Signal(bool)
|
|
248
|
+
zoomInClicked = Signal()
|
|
249
|
+
zoomOutClicked = Signal()
|
|
250
|
+
|
|
251
|
+
def __init__(self, parent: Optional[QWidget] = None):
|
|
252
|
+
super().__init__(parent)
|
|
253
|
+
self.setObjectName("NodeViewOverlayControls")
|
|
254
|
+
self.setAttribute(Qt.WA_StyledBackground, True)
|
|
255
|
+
|
|
256
|
+
layout = QHBoxLayout(self)
|
|
257
|
+
# Bigger spacing to visually add padding around buttons
|
|
258
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
259
|
+
layout.setSpacing(8)
|
|
260
|
+
|
|
261
|
+
cfg = self._cfg()
|
|
262
|
+
|
|
263
|
+
# Grab (toggle)
|
|
264
|
+
self.btnGrab = QPushButton(self)
|
|
265
|
+
self.btnGrab.setCheckable(True)
|
|
266
|
+
self.btnGrab.setToolTip(cfg.overlay_grab_tooltip())
|
|
267
|
+
self.btnGrab.setIcon(QIcon(":/icons/drag.svg"))
|
|
268
|
+
self.btnGrab.setIconSize(QSize(20, 20))
|
|
269
|
+
self.btnGrab.setMinimumSize(32, 32)
|
|
270
|
+
|
|
271
|
+
# Zoom Out (placed before Zoom In)
|
|
272
|
+
self.btnZoomOut = QPushButton(self)
|
|
273
|
+
self.btnZoomOut.setToolTip(cfg.overlay_zoom_out_tooltip())
|
|
274
|
+
self.btnZoomOut.setIcon(QIcon(":/icons/zoom_out.svg"))
|
|
275
|
+
self.btnZoomOut.setIconSize(QSize(20, 20))
|
|
276
|
+
self.btnZoomOut.setMinimumSize(32, 32)
|
|
277
|
+
|
|
278
|
+
# Zoom In
|
|
279
|
+
self.btnZoomIn = QPushButton(self)
|
|
280
|
+
self.btnZoomIn.setToolTip(cfg.overlay_zoom_in_tooltip())
|
|
281
|
+
self.btnZoomIn.setIcon(QIcon(":/icons/zoom_in.svg"))
|
|
282
|
+
self.btnZoomIn.setIconSize(QSize(20, 20))
|
|
283
|
+
self.btnZoomIn.setMinimumSize(32, 32)
|
|
284
|
+
|
|
285
|
+
layout.addWidget(self.btnGrab)
|
|
286
|
+
layout.addWidget(self.btnZoomIn)
|
|
287
|
+
layout.addWidget(self.btnZoomOut)
|
|
288
|
+
|
|
289
|
+
self.btnGrab.toggled.connect(self.grabToggled.emit)
|
|
290
|
+
self.btnZoomIn.clicked.connect(self.zoomInClicked.emit)
|
|
291
|
+
self.btnZoomOut.clicked.connect(self.zoomOutClicked.emit)
|
|
292
|
+
|
|
293
|
+
self.show()
|
|
294
|
+
|
|
295
|
+
def _cfg(self) -> EditorConfig:
|
|
296
|
+
p = self.parent()
|
|
297
|
+
c = getattr(p, "config", None) if p is not None else None
|
|
298
|
+
return c if isinstance(c, EditorConfig) else EditorConfig()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class NodeViewStatusLabel(QWidget):
|
|
302
|
+
"""Fixed status overlay pinned to bottom-left that shows node type counts."""
|
|
303
|
+
|
|
304
|
+
def __init__(self, parent: Optional[QWidget] = None):
|
|
305
|
+
super().__init__(parent)
|
|
306
|
+
self.setObjectName("NodeViewStatusLabel")
|
|
307
|
+
self.setAttribute(Qt.WA_StyledBackground, True)
|
|
308
|
+
|
|
309
|
+
self._lbl = QLabel(self)
|
|
310
|
+
cfg = self._cfg()
|
|
311
|
+
self._lbl.setText(cfg.status_no_nodes())
|
|
312
|
+
|
|
313
|
+
layout = QHBoxLayout(self)
|
|
314
|
+
layout.setContentsMargins(8, 4, 8, 4) # some padding around the text
|
|
315
|
+
layout.setSpacing(0)
|
|
316
|
+
layout.addWidget(self._lbl)
|
|
317
|
+
|
|
318
|
+
self.adjustSize()
|
|
319
|
+
self.show()
|
|
320
|
+
|
|
321
|
+
def _cfg(self) -> EditorConfig:
|
|
322
|
+
p = self.parent()
|
|
323
|
+
c = getattr(p, "config", None) if p is not None else None
|
|
324
|
+
return c if isinstance(c, EditorConfig) else EditorConfig()
|
|
325
|
+
|
|
326
|
+
def set_text(self, text: str):
|
|
327
|
+
self._lbl.setText(text)
|
|
328
|
+
# Safe: adjustSize() uses sizeHint(), which no longer calls adjustSize()
|
|
329
|
+
self.adjustSize()
|
|
330
|
+
|
|
331
|
+
def sizeHint(self):
|
|
332
|
+
"""Return hint based on layout/label without calling adjustSize()."""
|
|
333
|
+
if self.layout() is not None:
|
|
334
|
+
return self.layout().sizeHint()
|
|
335
|
+
return self._lbl.sizeHint()
|
|
336
|
+
|
|
221
337
|
|
|
222
338
|
class NodeGraphicsScene(QGraphicsScene):
|
|
223
339
|
"""Graphics scene extended with custom context menu emission."""
|
|
224
340
|
sceneContextRequested = Signal(QPointF)
|
|
225
341
|
|
|
226
342
|
def __init__(self, parent: Optional[QObject] = None):
|
|
227
|
-
"""Initialize the scene and set a very large scene rect.
|
|
228
|
-
|
|
229
|
-
Using a large default rect avoids sudden scene rect changes while panning/zooming.
|
|
230
|
-
"""
|
|
343
|
+
"""Initialize the scene and set a very large scene rect."""
|
|
231
344
|
super().__init__(parent)
|
|
232
345
|
self.setSceneRect(-5000, -5000, 10000, 10000)
|
|
233
346
|
|
|
234
347
|
def contextMenuEvent(self, event):
|
|
235
|
-
"""Emit a scene-level context menu request when clicking empty space.
|
|
236
|
-
|
|
237
|
-
If the click is not on any item, the signal sceneContextRequested is emitted with
|
|
238
|
-
the scene position. Otherwise, default handling is used (propagating to items).
|
|
239
|
-
"""
|
|
348
|
+
"""Emit a scene-level context menu request when clicking empty space."""
|
|
240
349
|
transform = self.views()[0].transform() if self.views() else QTransform()
|
|
241
350
|
item = self.itemAt(event.scenePos(), transform)
|
|
242
351
|
if item is None:
|
|
243
|
-
|
|
352
|
+
# Respect external edit permission if available on parent editor
|
|
353
|
+
editor = self.parent()
|
|
354
|
+
allowed = True
|
|
355
|
+
try:
|
|
356
|
+
if hasattr(editor, "editing_allowed") and callable(editor.editing_allowed):
|
|
357
|
+
allowed = bool(editor.editing_allowed())
|
|
358
|
+
except Exception:
|
|
359
|
+
allowed = False
|
|
360
|
+
if allowed:
|
|
361
|
+
self.sceneContextRequested.emit(event.scenePos())
|
|
244
362
|
event.accept()
|
|
245
363
|
return
|
|
246
|
-
super().contextMenuEvent(event)
|
|
247
|
-
|
|
364
|
+
super().contextMenuEvent(event)
|
|
@@ -311,7 +311,7 @@ class AddButton(QPushButton):
|
|
|
311
311
|
)
|
|
312
312
|
self.setObjectName('tab-add')
|
|
313
313
|
self.setProperty('tabAdd', True)
|
|
314
|
-
self.setToolTip(trans('action.tab.add.chat'))
|
|
314
|
+
self.setToolTip(trans('action.tab.add.chat.tooltip'))
|
|
315
315
|
|
|
316
316
|
def mousePressEvent(self, event):
|
|
317
317
|
"""
|
|
@@ -94,8 +94,8 @@ class ChatInput(QTextEdit):
|
|
|
94
94
|
key="web",
|
|
95
95
|
icon=self.ICON_WEB_OFF,
|
|
96
96
|
alt_icon=self.ICON_WEB_ON,
|
|
97
|
-
tooltip=trans('icon.remote_tool.web'),
|
|
98
|
-
alt_tooltip=trans('icon.remote_tool.web'),
|
|
97
|
+
tooltip=trans('icon.remote_tool.web.disabled'),
|
|
98
|
+
alt_tooltip=trans('icon.remote_tool.web.enabled'),
|
|
99
99
|
callback=self.action_toggle_web,
|
|
100
100
|
visible=True,
|
|
101
101
|
)
|
pygpt_net/utils.py
CHANGED
|
@@ -6,15 +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.25 12:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
|
+
import math
|
|
16
|
+
|
|
15
17
|
from datetime import datetime
|
|
16
18
|
from contextlib import contextmanager
|
|
17
19
|
from typing import Any
|
|
20
|
+
from decimal import Decimal, ROUND_HALF_UP, InvalidOperation
|
|
21
|
+
from typing import Sequence
|
|
18
22
|
|
|
19
23
|
from PySide6 import QtCore, QtGui
|
|
20
24
|
from PySide6.QtWidgets import QApplication
|
|
@@ -381,4 +385,112 @@ def mem_clean(force: bool = False) -> bool:
|
|
|
381
385
|
'''
|
|
382
386
|
except Exception as e:
|
|
383
387
|
print(e)
|
|
384
|
-
return ok
|
|
388
|
+
return ok
|
|
389
|
+
|
|
390
|
+
def short_num(value,
|
|
391
|
+
*,
|
|
392
|
+
base: int = 1000,
|
|
393
|
+
suffixes: Sequence[str] = ("", "k", "M", "B", "T", "P", "E"),
|
|
394
|
+
max_decimals: int = 1,
|
|
395
|
+
decimal_sep: str = ",") -> str:
|
|
396
|
+
"""
|
|
397
|
+
Compact human-readable formatter for numbers with suffixes (k, M, B, ...).
|
|
398
|
+
|
|
399
|
+
Rules:
|
|
400
|
+
- abs(value) < base -> return the value without a suffix
|
|
401
|
+
- otherwise -> divide by base^n and append suffix
|
|
402
|
+
- decimals:
|
|
403
|
+
< 10 -> up to 2 (bounded by max_decimals)
|
|
404
|
+
< 100 -> up to 1 (bounded by max_decimals)
|
|
405
|
+
>= 100 -> 0
|
|
406
|
+
- rounding: ROUND_HALF_UP
|
|
407
|
+
- auto "carry" to the next suffix after rounding (e.g., 999.95k -> 1M)
|
|
408
|
+
|
|
409
|
+
Params:
|
|
410
|
+
- base: 1000 for general numbers, 1024 for bytes, etc.
|
|
411
|
+
- suffixes: first item must be "" (for < base)
|
|
412
|
+
- max_decimals: upper bound for fractional digits
|
|
413
|
+
- decimal_sep: decimal separator in the output
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
# --- helpers hidden inside (closure) ---
|
|
417
|
+
|
|
418
|
+
def _to_decimal(v) -> Decimal:
|
|
419
|
+
"""Convert supported inputs to Decimal; keep NaN/Inf as-is."""
|
|
420
|
+
if isinstance(v, Decimal):
|
|
421
|
+
return v
|
|
422
|
+
if isinstance(v, int):
|
|
423
|
+
return Decimal(v)
|
|
424
|
+
if isinstance(v, float):
|
|
425
|
+
if math.isnan(v):
|
|
426
|
+
return Decimal("NaN")
|
|
427
|
+
if math.isinf(v):
|
|
428
|
+
return Decimal("Infinity") if v > 0 else Decimal("-Infinity")
|
|
429
|
+
# str() to avoid binary float artefacts
|
|
430
|
+
return Decimal(str(v))
|
|
431
|
+
try:
|
|
432
|
+
return Decimal(str(v))
|
|
433
|
+
except (InvalidOperation, ValueError, TypeError):
|
|
434
|
+
raise TypeError("short_num(value): value must be numeric (int/float/Decimal) "
|
|
435
|
+
"or a string parsable to a number.")
|
|
436
|
+
|
|
437
|
+
def _decimals_for(scaled: Decimal) -> int:
|
|
438
|
+
"""Pick number of decimals based on magnitude."""
|
|
439
|
+
if max_decimals <= 0:
|
|
440
|
+
return 0
|
|
441
|
+
if scaled < 10:
|
|
442
|
+
return min(2, max_decimals)
|
|
443
|
+
if scaled < 100:
|
|
444
|
+
return min(1, max_decimals)
|
|
445
|
+
return 0
|
|
446
|
+
|
|
447
|
+
def _round_dec(d: Decimal, decimals: int) -> Decimal:
|
|
448
|
+
"""ROUND_HALF_UP to the requested decimals."""
|
|
449
|
+
if decimals <= 0:
|
|
450
|
+
return d.quantize(Decimal("1"), rounding=ROUND_HALF_UP)
|
|
451
|
+
q = Decimal(1).scaleb(-decimals) # 10**(-decimals)
|
|
452
|
+
return d.quantize(q, rounding=ROUND_HALF_UP)
|
|
453
|
+
|
|
454
|
+
def _strip_trailing_zeros(s: str) -> str:
|
|
455
|
+
"""Remove trailing zeros and trailing decimal point if needed."""
|
|
456
|
+
if "." in s:
|
|
457
|
+
s = s.rstrip("0").rstrip(".")
|
|
458
|
+
return s
|
|
459
|
+
|
|
460
|
+
# --- main logic ---
|
|
461
|
+
|
|
462
|
+
d = _to_decimal(value)
|
|
463
|
+
if not d.is_finite():
|
|
464
|
+
# For NaN/Inf just echo back Python-ish text
|
|
465
|
+
return str(value)
|
|
466
|
+
|
|
467
|
+
sign = "-" if d < 0 else ""
|
|
468
|
+
d = abs(d)
|
|
469
|
+
|
|
470
|
+
# For values below base, return "as is" (normalized, no suffix)
|
|
471
|
+
if d < base:
|
|
472
|
+
s = _strip_trailing_zeros(f"{d.normalize():f}")
|
|
473
|
+
return (sign + s).replace(".", decimal_sep)
|
|
474
|
+
|
|
475
|
+
# Find initial suffix tier
|
|
476
|
+
idx = 0
|
|
477
|
+
last_idx = len(suffixes) - 1
|
|
478
|
+
while d >= base and idx < last_idx:
|
|
479
|
+
d = d / base
|
|
480
|
+
idx += 1
|
|
481
|
+
|
|
482
|
+
# Choose decimals, round, then handle possible carry to next suffix
|
|
483
|
+
decimals = _decimals_for(d)
|
|
484
|
+
d = _round_dec(d, decimals)
|
|
485
|
+
|
|
486
|
+
while d >= base and idx < last_idx:
|
|
487
|
+
# Carry over (e.g., 999.95k -> 1000.00k -> 1.00M)
|
|
488
|
+
d = d / base
|
|
489
|
+
idx += 1
|
|
490
|
+
decimals = _decimals_for(d)
|
|
491
|
+
d = _round_dec(d, decimals)
|
|
492
|
+
|
|
493
|
+
# Format, trim zeros, apply custom decimal separator, attach suffix
|
|
494
|
+
out = f"{d:.{decimals}f}"
|
|
495
|
+
out = _strip_trailing_zeros(out).replace(".", decimal_sep)
|
|
496
|
+
return f"{sign}{out}{suffixes[idx]}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pygpt-net
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.61
|
|
4
4
|
Summary: Desktop AI Assistant powered by: OpenAI GPT-5, GPT-4, o1, o3, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, MCP, internet access, file handling, command execution and more.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: ai,api,api key,app,assistant,bielik,chat,chatbot,chatgpt,claude,dall-e,deepseek,desktop,gemini,gpt,gpt-3.5,gpt-4,gpt-4-vision,gpt-4o,gpt-5,gpt-oss,gpt3.5,gpt4,grok,langchain,llama-index,llama3,mistral,o1,o3,ollama,openai,presets,py-gpt,py_gpt,pygpt,pyside,qt,text completion,tts,ui,vision,whisper
|
|
@@ -117,7 +117,7 @@ Description-Content-Type: text/markdown
|
|
|
117
117
|
|
|
118
118
|
[](https://snapcraft.io/pygpt)
|
|
119
119
|
|
|
120
|
-
Release: **2.6.
|
|
120
|
+
Release: **2.6.61** | build: **2025-09-26** | Python: **>=3.10, <3.14**
|
|
121
121
|
|
|
122
122
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
|
123
123
|
>
|
|
@@ -2224,7 +2224,8 @@ Rules:
|
|
|
2224
2224
|
- content must contain the user-facing answer (you may include structured data as JSON or Markdown inside content).
|
|
2225
2225
|
- Do NOT add any commentary outside of the JSON. No leading or trailing text.
|
|
2226
2226
|
- If using tools, still return the final JSON with tool results summarized in content.
|
|
2227
|
-
- Human-friendly route names: <
|
|
2227
|
+
- Human-friendly route names: <names>
|
|
2228
|
+
- Human-friendly route roles (optional): <roles>
|
|
2228
2229
|
|
|
2229
2230
|
<here begins your system instruction>
|
|
2230
2231
|
```
|
|
@@ -2455,8 +2456,6 @@ Config -> Settings...
|
|
|
2455
2456
|
|
|
2456
2457
|
- `Store dialog window positions`: Enable or disable dialogs positions store/restore, Default: True.
|
|
2457
2458
|
|
|
2458
|
-
- `Use theme colors in chat window`: Use color theme in chat window, Default: True.
|
|
2459
|
-
|
|
2460
2459
|
**Code syntax**
|
|
2461
2460
|
|
|
2462
2461
|
- `Code syntax highlight`: Syntax highlight theme in code blocks. `WebEngine / Chromium` render mode only.
|
|
@@ -2523,8 +2522,6 @@ Config -> Settings...
|
|
|
2523
2522
|
|
|
2524
2523
|
- `Open URLs in built-in browser`: Enable this option to open all URLs in the built-in browser (Chromium) instead of an external browser. Default: False.
|
|
2525
2524
|
|
|
2526
|
-
- `Convert lists to paragraphs`: If enabled, lists (ul, ol) will be converted to paragraphs (p), Default: True.
|
|
2527
|
-
|
|
2528
2525
|
- `Model used for auto-summary`: Model used for context auto-summary (generating titles in context list) (default: *gpt-4o-mini*). **Tip:** If you prefer to use local models, you should change the model here as well
|
|
2529
2526
|
|
|
2530
2527
|
**Remote tools**
|
|
@@ -3724,6 +3721,13 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3724
3721
|
|
|
3725
3722
|
## Recent changes:
|
|
3726
3723
|
|
|
3724
|
+
**2.6.61 (2025-09-26)**
|
|
3725
|
+
|
|
3726
|
+
- Enhanced the agents node editor, custom agent flow, and instruction following.
|
|
3727
|
+
- Added drag-and-drop and reordering functionality to the presets list.
|
|
3728
|
+
- Added statistics for response tokens, including time elapsed and tokens per second.
|
|
3729
|
+
- Improved UI/UX.
|
|
3730
|
+
|
|
3727
3731
|
**2.6.60 (2025-09-25)**
|
|
3728
3732
|
|
|
3729
3733
|
- Added a new tool: Agents Builder - allowing visual design of agent workflows using nodes - available in Tools -> Agents Builder (beta).
|
|
@@ -3744,93 +3748,6 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3744
3748
|
- Improved: The local web search plugin has been enhanced to retrieve multiple URLs at once.
|
|
3745
3749
|
- Added: Use proxy switch in Settings.
|
|
3746
3750
|
|
|
3747
|
-
**2.6.56 (2025-09-22)**
|
|
3748
|
-
|
|
3749
|
-
- Optimized: Memory usage and performance in streaming and rendering large contexts.
|
|
3750
|
-
- Added: Copy to clipboard functionality in user messages.
|
|
3751
|
-
- Added: Manual scroll in plain-text mode.
|
|
3752
|
-
|
|
3753
|
-
**2.6.55 (2025-09-18)**
|
|
3754
|
-
|
|
3755
|
-
- Fixed: Unnecessary context loading from the database.
|
|
3756
|
-
- Optimized: Token count in the input field.
|
|
3757
|
-
|
|
3758
|
-
**2.6.54 (2025-09-18)**
|
|
3759
|
-
|
|
3760
|
-
- Added: Remote tools (like web search) are now also available in the Chat with Files and Agents (LlamaIndex) modes.
|
|
3761
|
-
- Added: Two new plugins: Wolfram Alpha and OpenStreetMap.
|
|
3762
|
-
- Fixed: Enabled local file-like schemes in links/images in the markdown-it parser.
|
|
3763
|
-
|
|
3764
|
-
**2.6.53 (2025-09-17)**
|
|
3765
|
-
|
|
3766
|
-
- Added: An icon to enable/disable the web search remote tool in the icon bar, along with remote web search functionality in OpenRouter (#135).
|
|
3767
|
-
- Added: The ability to mute audio in real-time mode via the audio icon.
|
|
3768
|
-
|
|
3769
|
-
**2.6.52 (2025-09-17)**
|
|
3770
|
-
|
|
3771
|
-
- Added MCP plugin: Provides access to remote tools via the Model Context Protocol (MCP), including stdio, SSE, and Streamable HTTP transports, with per-server allow/deny filtering, Authorization header support, and a tools cache.
|
|
3772
|
-
- Fixed: tab tooltips reload on profile switch.
|
|
3773
|
-
|
|
3774
|
-
**2.6.51 (2025-09-16)**
|
|
3775
|
-
|
|
3776
|
-
- Fix: Automatically reloading calendar notes.
|
|
3777
|
-
- Fix: Context menu CSS background color for calendar items.
|
|
3778
|
-
|
|
3779
|
-
**2.6.50 (2025-09-16)**
|
|
3780
|
-
|
|
3781
|
-
- Optimized: Improved memory cleanup when switching profiles and unloading tabs.
|
|
3782
|
-
- Fix: Resolved missing PID data in text output.
|
|
3783
|
-
- Fix: Enhanced real-time parsing of execute tags.
|
|
3784
|
-
|
|
3785
|
-
**2.6.49 (2025-09-16)**
|
|
3786
|
-
|
|
3787
|
-
- Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
|
|
3788
|
-
|
|
3789
|
-
**2.6.48 (2025-09-15)**
|
|
3790
|
-
|
|
3791
|
-
- Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
|
|
3792
|
-
|
|
3793
|
-
**2.6.47 (2025-09-15)**
|
|
3794
|
-
|
|
3795
|
-
- Improved: Parsing of custom markup tags.
|
|
3796
|
-
- Optimized: Switching profiles.
|
|
3797
|
-
|
|
3798
|
-
**2.6.46 (2025-09-15)**
|
|
3799
|
-
|
|
3800
|
-
- Added: Global proxy settings for all API SDKs.
|
|
3801
|
-
- Fixed: xAI client configuration in Chat with Files.
|
|
3802
|
-
- Fixed: Top margin in streaming container.
|
|
3803
|
-
- Refactored: Debug workers.
|
|
3804
|
-
|
|
3805
|
-
**2.6.45 (2025-09-13)**
|
|
3806
|
-
|
|
3807
|
-
- Improved: Parsing of custom markup in the stream.
|
|
3808
|
-
- Improved: Message block parsing moved to JavaScript.
|
|
3809
|
-
|
|
3810
|
-
**2.6.44 (2025-09-12)**
|
|
3811
|
-
|
|
3812
|
-
- Added: Auto-collapse for large user input blocks.
|
|
3813
|
-
- Added: Configuration for syntax highlighting intervals.
|
|
3814
|
-
- Improved: Visibility of label icons.
|
|
3815
|
-
- Improved: Scrolling of code blocks.
|
|
3816
|
-
- Fixed: Parsing of quotes in custom markdown blocks.
|
|
3817
|
-
|
|
3818
|
-
**2.6.43 (2025-09-12)**
|
|
3819
|
-
|
|
3820
|
-
- Fixed: preset restoration when switching profiles.
|
|
3821
|
-
- Improved: faster application launch and exit.
|
|
3822
|
-
|
|
3823
|
-
**2.6.42 (2025-09-11)**
|
|
3824
|
-
|
|
3825
|
-
- Fixed: Save/load zoom factor in the chat window when switched via Ctrl + mouse wheel.
|
|
3826
|
-
- Fixed: Break the first line in code blocks.
|
|
3827
|
-
- Added: Configuration options for syntax highlight limits.
|
|
3828
|
-
- Updated: SVG icons.
|
|
3829
|
-
|
|
3830
|
-
**2.6.41 (2025-09-11)**
|
|
3831
|
-
|
|
3832
|
-
- Rendering engine optimizations: markdown parsing moved to JS, reduced CPU usage, added auto-memory clearing, and more.
|
|
3833
|
-
|
|
3834
3751
|
# Credits and links
|
|
3835
3752
|
|
|
3836
3753
|
**Official website:** <https://pygpt.net>
|