pygpt-net 2.6.21__py3-none-any.whl → 2.6.23__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 +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +3 -1
- pygpt_net/controller/__init__.py +4 -8
- pygpt_net/controller/access/voice.py +2 -2
- pygpt_net/controller/agent/llama.py +3 -0
- pygpt_net/controller/assistant/batch.py +2 -3
- pygpt_net/controller/assistant/editor.py +2 -2
- pygpt_net/controller/assistant/files.py +2 -3
- pygpt_net/controller/assistant/store.py +2 -2
- pygpt_net/controller/audio/audio.py +2 -2
- pygpt_net/controller/chat/response.py +4 -0
- pygpt_net/controller/ctx/ctx.py +2 -1
- pygpt_net/controller/files/files.py +24 -55
- pygpt_net/controller/idx/indexer.py +85 -76
- pygpt_net/controller/lang/lang.py +52 -34
- pygpt_net/controller/model/importer.py +2 -2
- pygpt_net/controller/notepad/notepad.py +86 -84
- pygpt_net/controller/plugins/settings.py +3 -4
- pygpt_net/controller/settings/profile.py +105 -124
- pygpt_net/controller/theme/menu.py +154 -57
- pygpt_net/controller/theme/nodes.py +51 -44
- pygpt_net/controller/theme/theme.py +33 -9
- pygpt_net/controller/tools/tools.py +2 -2
- pygpt_net/controller/ui/tabs.py +2 -3
- pygpt_net/core/agents/observer/evaluation.py +2 -2
- pygpt_net/core/agents/runners/loop.py +1 -0
- pygpt_net/core/bridge/bridge.py +2 -0
- pygpt_net/core/ctx/container.py +13 -12
- pygpt_net/core/ctx/output.py +7 -4
- pygpt_net/core/debug/console/console.py +2 -2
- pygpt_net/core/filesystem/actions.py +1 -2
- pygpt_net/core/filesystem/opener.py +261 -0
- pygpt_net/core/filesystem/url.py +13 -10
- pygpt_net/core/platforms/platforms.py +5 -4
- pygpt_net/core/render/plain/helpers.py +2 -5
- pygpt_net/core/render/plain/renderer.py +26 -30
- pygpt_net/core/render/web/body.py +1 -1
- pygpt_net/core/settings/settings.py +43 -13
- pygpt_net/core/tabs/tabs.py +20 -13
- pygpt_net/data/config/config.json +4 -4
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/web-blocks.dark.css +7 -1
- pygpt_net/data/css/web-blocks.light.css +5 -2
- pygpt_net/data/css/web-chatgpt.dark.css +7 -1
- pygpt_net/data/css/web-chatgpt.light.css +3 -0
- pygpt_net/data/css/web-chatgpt_wide.dark.css +7 -1
- pygpt_net/data/css/web-chatgpt_wide.light.css +3 -0
- pygpt_net/data/locale/locale.de.ini +5 -1
- pygpt_net/data/locale/locale.en.ini +5 -1
- pygpt_net/data/locale/locale.es.ini +5 -1
- pygpt_net/data/locale/locale.fr.ini +5 -1
- pygpt_net/data/locale/locale.it.ini +5 -1
- pygpt_net/data/locale/locale.pl.ini +6 -4
- pygpt_net/data/locale/locale.uk.ini +5 -1
- pygpt_net/data/locale/locale.zh.ini +5 -1
- pygpt_net/plugin/twitter/plugin.py +2 -2
- pygpt_net/provider/core/config/patch.py +12 -1
- pygpt_net/tools/audio_transcriber/ui/dialogs.py +44 -54
- pygpt_net/tools/code_interpreter/body.py +1 -2
- pygpt_net/tools/code_interpreter/tool.py +7 -4
- pygpt_net/tools/code_interpreter/ui/html.py +1 -3
- pygpt_net/tools/code_interpreter/ui/widgets.py +2 -3
- pygpt_net/tools/html_canvas/ui/widgets.py +1 -3
- pygpt_net/tools/image_viewer/ui/dialogs.py +40 -37
- pygpt_net/tools/indexer/ui/widgets.py +2 -4
- pygpt_net/tools/media_player/tool.py +2 -5
- pygpt_net/tools/media_player/ui/widgets.py +60 -36
- pygpt_net/tools/text_editor/ui/widgets.py +18 -19
- pygpt_net/tools/translator/ui/widgets.py +39 -35
- pygpt_net/ui/base/context_menu.py +9 -4
- pygpt_net/ui/dialog/db.py +1 -3
- pygpt_net/ui/dialog/models.py +1 -3
- pygpt_net/ui/dialog/models_importer.py +2 -4
- pygpt_net/ui/dialogs.py +34 -30
- pygpt_net/ui/layout/chat/attachments.py +72 -84
- pygpt_net/ui/layout/chat/attachments_ctx.py +40 -44
- pygpt_net/ui/layout/chat/attachments_uploaded.py +36 -39
- pygpt_net/ui/layout/chat/calendar.py +100 -70
- pygpt_net/ui/layout/chat/chat.py +23 -17
- pygpt_net/ui/layout/chat/input.py +95 -118
- pygpt_net/ui/layout/chat/output.py +100 -162
- pygpt_net/ui/layout/chat/painter.py +89 -61
- pygpt_net/ui/layout/ctx/ctx_list.py +43 -52
- pygpt_net/ui/layout/status.py +23 -14
- pygpt_net/ui/layout/toolbox/agent.py +27 -38
- pygpt_net/ui/layout/toolbox/agent_llama.py +41 -45
- pygpt_net/ui/layout/toolbox/assistants.py +42 -38
- pygpt_net/ui/layout/toolbox/computer_env.py +32 -23
- pygpt_net/ui/layout/toolbox/footer.py +13 -16
- pygpt_net/ui/layout/toolbox/image.py +18 -21
- pygpt_net/ui/layout/toolbox/indexes.py +46 -89
- pygpt_net/ui/layout/toolbox/mode.py +20 -7
- pygpt_net/ui/layout/toolbox/model.py +12 -10
- pygpt_net/ui/layout/toolbox/presets.py +68 -52
- pygpt_net/ui/layout/toolbox/prompt.py +31 -58
- pygpt_net/ui/layout/toolbox/toolbox.py +25 -21
- pygpt_net/ui/layout/toolbox/vision.py +20 -22
- pygpt_net/ui/main.py +2 -4
- pygpt_net/ui/menu/about.py +64 -84
- pygpt_net/ui/menu/audio.py +87 -63
- pygpt_net/ui/menu/config.py +121 -127
- pygpt_net/ui/menu/debug.py +69 -76
- pygpt_net/ui/menu/file.py +32 -35
- pygpt_net/ui/menu/menu.py +2 -3
- pygpt_net/ui/menu/plugins.py +69 -33
- pygpt_net/ui/menu/theme.py +45 -46
- pygpt_net/ui/menu/tools.py +56 -60
- pygpt_net/ui/menu/video.py +20 -25
- pygpt_net/ui/tray.py +1 -2
- pygpt_net/ui/widget/audio/bar.py +1 -3
- pygpt_net/ui/widget/audio/input_button.py +3 -4
- pygpt_net/ui/widget/calendar/select.py +1 -2
- pygpt_net/ui/widget/dialog/base.py +12 -9
- pygpt_net/ui/widget/dialog/editor_file.py +20 -23
- pygpt_net/ui/widget/dialog/find.py +25 -24
- pygpt_net/ui/widget/dialog/profile.py +57 -53
- pygpt_net/ui/widget/draw/painter.py +62 -93
- pygpt_net/ui/widget/element/button.py +42 -30
- pygpt_net/ui/widget/element/checkbox.py +23 -15
- pygpt_net/ui/widget/element/group.py +6 -5
- pygpt_net/ui/widget/element/labels.py +1 -2
- pygpt_net/ui/widget/filesystem/explorer.py +93 -102
- pygpt_net/ui/widget/image/display.py +1 -2
- pygpt_net/ui/widget/lists/assistant.py +1 -2
- pygpt_net/ui/widget/lists/attachment.py +1 -2
- pygpt_net/ui/widget/lists/attachment_ctx.py +1 -2
- pygpt_net/ui/widget/lists/context.py +2 -4
- pygpt_net/ui/widget/lists/index.py +1 -2
- pygpt_net/ui/widget/lists/model.py +1 -2
- pygpt_net/ui/widget/lists/model_editor.py +1 -2
- pygpt_net/ui/widget/lists/model_importer.py +1 -2
- pygpt_net/ui/widget/lists/preset.py +1 -2
- pygpt_net/ui/widget/lists/preset_plugins.py +1 -2
- pygpt_net/ui/widget/lists/profile.py +1 -2
- pygpt_net/ui/widget/lists/uploaded.py +1 -2
- pygpt_net/ui/widget/option/checkbox.py +2 -4
- pygpt_net/ui/widget/option/checkbox_list.py +1 -4
- pygpt_net/ui/widget/option/cmd.py +1 -4
- pygpt_net/ui/widget/option/dictionary.py +25 -28
- pygpt_net/ui/widget/option/input.py +1 -3
- pygpt_net/ui/widget/tabs/Input.py +16 -12
- pygpt_net/ui/widget/tabs/body.py +5 -3
- pygpt_net/ui/widget/tabs/layout.py +41 -28
- pygpt_net/ui/widget/tabs/output.py +442 -85
- pygpt_net/ui/widget/textarea/calendar_note.py +1 -2
- pygpt_net/ui/widget/textarea/editor.py +41 -73
- pygpt_net/ui/widget/textarea/find.py +11 -10
- pygpt_net/ui/widget/textarea/html.py +3 -6
- pygpt_net/ui/widget/textarea/input.py +134 -69
- pygpt_net/ui/widget/textarea/notepad.py +54 -38
- pygpt_net/ui/widget/textarea/output.py +65 -54
- pygpt_net/ui/widget/textarea/search_input.py +5 -4
- pygpt_net/ui/widget/textarea/web.py +2 -4
- pygpt_net/ui/widget/vision/camera.py +2 -31
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/METADATA +38 -174
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/RECORD +160 -159
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.21.dist-info → pygpt_net-2.6.23.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
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.08.25 18:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
|
|
17
|
+
from PySide6.QtCore import QUrl
|
|
18
|
+
from PySide6.QtGui import QDesktopServices
|
|
19
|
+
|
|
20
|
+
# QtDBus is optional and may not be available on all systems
|
|
21
|
+
try:
|
|
22
|
+
from PySide6.QtDBus import QDBusInterface, QDBusConnection, QDBusMessage
|
|
23
|
+
HAS_QT_DBUS = True
|
|
24
|
+
except Exception:
|
|
25
|
+
HAS_QT_DBUS = False
|
|
26
|
+
|
|
27
|
+
IS_WINDOWS = sys.platform.startswith("win")
|
|
28
|
+
IS_MAC = sys.platform == "darwin"
|
|
29
|
+
IS_LINUX = sys.platform.startswith("linux")
|
|
30
|
+
|
|
31
|
+
class Opener:
|
|
32
|
+
# ===== public API =====
|
|
33
|
+
@staticmethod
|
|
34
|
+
def open_path(path: str, reveal: bool = False) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Open file or directory in the system file manager or default application.
|
|
37
|
+
|
|
38
|
+
:param path: Path to file or directory
|
|
39
|
+
:param reveal: If True and path is a file, reveal it in the file manager
|
|
40
|
+
:return: True if successful, False otherwise
|
|
41
|
+
"""
|
|
42
|
+
p = os.path.abspath(path)
|
|
43
|
+
|
|
44
|
+
if IS_WINDOWS:
|
|
45
|
+
if reveal and os.path.isfile(p):
|
|
46
|
+
return Opener._reveal_windows(p)
|
|
47
|
+
if os.path.isdir(p):
|
|
48
|
+
return Opener._open_dir_windows(p)
|
|
49
|
+
return Opener._open_file_windows(p)
|
|
50
|
+
|
|
51
|
+
if IS_MAC:
|
|
52
|
+
if reveal and os.path.isfile(p):
|
|
53
|
+
return Opener._reveal_mac(p)
|
|
54
|
+
if os.path.isdir(p):
|
|
55
|
+
return Opener._open_dir_mac(p)
|
|
56
|
+
return Opener._open_file_mac(p)
|
|
57
|
+
|
|
58
|
+
# Linux
|
|
59
|
+
if reveal and os.path.isfile(p):
|
|
60
|
+
if Opener._reveal_linux(p):
|
|
61
|
+
return True
|
|
62
|
+
# fallback:
|
|
63
|
+
return Opener._open_dir_linux(os.path.dirname(p) or "/")
|
|
64
|
+
if os.path.isdir(p):
|
|
65
|
+
return Opener._open_dir_linux(p)
|
|
66
|
+
return Opener._open_file_linux(p)
|
|
67
|
+
|
|
68
|
+
# ===== Windows =====
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _open_dir_windows(path: str) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Open directory in Windows Explorer
|
|
73
|
+
|
|
74
|
+
:param path: Path to directory
|
|
75
|
+
:return: True if successful, False otherwise
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
os.startfile(path)
|
|
79
|
+
return True
|
|
80
|
+
except Exception:
|
|
81
|
+
try:
|
|
82
|
+
subprocess.Popen(["explorer", path])
|
|
83
|
+
return True
|
|
84
|
+
except Exception:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _reveal_windows(path: str) -> bool:
|
|
89
|
+
"""
|
|
90
|
+
Reveal file in Windows Explorer
|
|
91
|
+
|
|
92
|
+
:param path: Path to file
|
|
93
|
+
:return: True if successful, False otherwise
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
subprocess.Popen(["explorer", "/select,", os.path.normpath(path)])
|
|
97
|
+
return True
|
|
98
|
+
except Exception:
|
|
99
|
+
return Opener._open_dir_windows(os.path.dirname(path) or "C:\\")
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _open_file_windows(path: str) -> bool:
|
|
103
|
+
"""
|
|
104
|
+
Open file with default application
|
|
105
|
+
|
|
106
|
+
:param path: Path to file
|
|
107
|
+
:return: True if successful, False otherwise
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
os.startfile(path)
|
|
111
|
+
return True
|
|
112
|
+
except Exception:
|
|
113
|
+
# Qt
|
|
114
|
+
return QDesktopServices.openUrl(QUrl.fromLocalFile(path))
|
|
115
|
+
|
|
116
|
+
# ===== macOS =====
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _open_dir_mac(path: str) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Open directory in Finder
|
|
121
|
+
|
|
122
|
+
:param path: Path to directory
|
|
123
|
+
:return: True if successful, False otherwise
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
subprocess.Popen(["open", path])
|
|
127
|
+
return True
|
|
128
|
+
except Exception:
|
|
129
|
+
return QDesktopServices.openUrl(QUrl.fromLocalFile(path))
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def _reveal_mac(path: str) -> bool:
|
|
133
|
+
"""
|
|
134
|
+
Reveal file in Finder
|
|
135
|
+
|
|
136
|
+
:param path: Path to file
|
|
137
|
+
:return: True if successful, False otherwise
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
subprocess.Popen(["open", "-R", path])
|
|
141
|
+
return True
|
|
142
|
+
except Exception:
|
|
143
|
+
return Opener._open_dir_mac(os.path.dirname(path) or "/")
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _open_file_mac(path: str) -> bool:
|
|
147
|
+
"""
|
|
148
|
+
Open file with default application
|
|
149
|
+
|
|
150
|
+
:param path: Path to file
|
|
151
|
+
:return: True if successful, False otherwise
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
subprocess.Popen(["open", path])
|
|
155
|
+
return True
|
|
156
|
+
except Exception:
|
|
157
|
+
return QDesktopServices.openUrl(QUrl.fromLocalFile(path))
|
|
158
|
+
|
|
159
|
+
# ===== Linux =====
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _fm_iface():
|
|
162
|
+
"""
|
|
163
|
+
Get FileManager1 D-Bus interface if available
|
|
164
|
+
|
|
165
|
+
:return: QDBusInterface or None
|
|
166
|
+
"""
|
|
167
|
+
if not (IS_LINUX and HAS_QT_DBUS):
|
|
168
|
+
return None
|
|
169
|
+
bus = QDBusConnection.sessionBus()
|
|
170
|
+
iface = QDBusInterface(
|
|
171
|
+
"org.freedesktop.FileManager1",
|
|
172
|
+
"/org/freedesktop/FileManager1",
|
|
173
|
+
"org.freedesktop.FileManager1",
|
|
174
|
+
bus,
|
|
175
|
+
)
|
|
176
|
+
return iface if iface.isValid() else None
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _show_folders_dbus(paths):
|
|
180
|
+
"""
|
|
181
|
+
Show folders using D-Bus FileManager1 interface
|
|
182
|
+
|
|
183
|
+
:param paths: List of folder paths
|
|
184
|
+
:return: True if successful, False otherwise
|
|
185
|
+
"""
|
|
186
|
+
iface = Opener._fm_iface()
|
|
187
|
+
if not iface:
|
|
188
|
+
return False
|
|
189
|
+
uris = [QUrl.fromLocalFile(p).toString() for p in paths]
|
|
190
|
+
reply = iface.call("ShowFolders", uris, "")
|
|
191
|
+
return reply.type() != QDBusMessage.ErrorMessage
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _show_items_dbus(paths):
|
|
195
|
+
"""
|
|
196
|
+
Show items using D-Bus FileManager1 interface
|
|
197
|
+
|
|
198
|
+
:param paths: List of item paths
|
|
199
|
+
:return: True if successful, False otherwise
|
|
200
|
+
"""
|
|
201
|
+
iface = Opener._fm_iface()
|
|
202
|
+
if not iface:
|
|
203
|
+
return False
|
|
204
|
+
uris = [QUrl.fromLocalFile(p).toString() for p in paths]
|
|
205
|
+
reply = iface.call("ShowItems", uris, "")
|
|
206
|
+
return reply.type() != QDBusMessage.ErrorMessage
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _open_with_cli_linux(path):
|
|
210
|
+
"""
|
|
211
|
+
Open path using common CLI tools (xdg-open, gio)
|
|
212
|
+
|
|
213
|
+
:param path: Path to file or directory
|
|
214
|
+
:return: True if successful, False otherwise
|
|
215
|
+
"""
|
|
216
|
+
for cmd in (["xdg-open", path], ["gio", "open", path]):
|
|
217
|
+
if shutil.which(cmd[0]):
|
|
218
|
+
try:
|
|
219
|
+
subprocess.Popen(cmd)
|
|
220
|
+
return True
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _open_dir_linux(path: str) -> bool:
|
|
227
|
+
"""
|
|
228
|
+
Open directory in default file manager
|
|
229
|
+
|
|
230
|
+
:param path: Path to directory
|
|
231
|
+
:return: True if successful, False otherwise
|
|
232
|
+
"""
|
|
233
|
+
if Opener._show_folders_dbus([path]):
|
|
234
|
+
return True
|
|
235
|
+
if QDesktopServices.openUrl(QUrl.fromLocalFile(path)):
|
|
236
|
+
return True
|
|
237
|
+
return Opener._open_with_cli_linux(path)
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def _reveal_linux(path: str) -> bool:
|
|
241
|
+
"""
|
|
242
|
+
Reveal file in default file manager
|
|
243
|
+
|
|
244
|
+
:param path: Path to file
|
|
245
|
+
:return: True if successful, False otherwise
|
|
246
|
+
"""
|
|
247
|
+
if Opener._show_items_dbus([path]):
|
|
248
|
+
return True
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _open_file_linux(path: str) -> bool:
|
|
253
|
+
"""
|
|
254
|
+
Open file with default application
|
|
255
|
+
|
|
256
|
+
:param path: Path to file
|
|
257
|
+
:return: True if successful, False otherwise
|
|
258
|
+
"""
|
|
259
|
+
if QDesktopServices.openUrl(QUrl.fromLocalFile(path)):
|
|
260
|
+
return True
|
|
261
|
+
return Opener._open_with_cli_linux(path)
|
pygpt_net/core/filesystem/url.py
CHANGED
|
@@ -23,19 +23,12 @@ class Url:
|
|
|
23
23
|
|
|
24
24
|
def handle(self, url: QUrl):
|
|
25
25
|
"""
|
|
26
|
-
Handle URL
|
|
26
|
+
Handle URL, bridge action or local file
|
|
27
27
|
|
|
28
28
|
:param url: url
|
|
29
29
|
"""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'extra-code-copy',
|
|
33
|
-
'extra-copy',
|
|
34
|
-
'extra-delete',
|
|
35
|
-
'extra-edit',
|
|
36
|
-
'extra-join',
|
|
37
|
-
'extra-replay',
|
|
38
|
-
]
|
|
30
|
+
if not url.toString().strip():
|
|
31
|
+
return
|
|
39
32
|
|
|
40
33
|
# JS bridge
|
|
41
34
|
if url.toString().startswith('bridge://open_find'):
|
|
@@ -50,8 +43,18 @@ class Url:
|
|
|
50
43
|
pid = self.window.controller.ui.tabs.get_current_pid()
|
|
51
44
|
if pid in self.window.ui.nodes['output']:
|
|
52
45
|
self.window.ui.nodes['output'][pid].on_focus_js()
|
|
46
|
+
return
|
|
53
47
|
|
|
54
48
|
# -------------
|
|
49
|
+
extra_schemes = (
|
|
50
|
+
'extra-audio-read',
|
|
51
|
+
'extra-code-copy',
|
|
52
|
+
'extra-copy',
|
|
53
|
+
'extra-delete',
|
|
54
|
+
'extra-edit',
|
|
55
|
+
'extra-join',
|
|
56
|
+
'extra-replay'
|
|
57
|
+
)
|
|
55
58
|
|
|
56
59
|
# local file
|
|
57
60
|
if not url.scheme().startswith('http') and url.scheme() not in extra_schemes:
|
|
@@ -6,11 +6,12 @@
|
|
|
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.08.25 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import platform
|
|
13
13
|
import os
|
|
14
|
+
import sys
|
|
14
15
|
|
|
15
16
|
from PySide6 import QtCore, QtWidgets
|
|
16
17
|
from PySide6.QtSvg import QSvgRenderer
|
|
@@ -87,7 +88,7 @@ class Platforms:
|
|
|
87
88
|
|
|
88
89
|
:return: True if OS is Linux
|
|
89
90
|
"""
|
|
90
|
-
return self.get_os() == 'Linux'
|
|
91
|
+
return self.get_os() == 'Linux' or sys.platform.startswith("linux")
|
|
91
92
|
|
|
92
93
|
def is_mac(self) -> bool:
|
|
93
94
|
"""
|
|
@@ -95,7 +96,7 @@ class Platforms:
|
|
|
95
96
|
|
|
96
97
|
:return: True if OS is MacOS
|
|
97
98
|
"""
|
|
98
|
-
return self.get_os() == 'Darwin'
|
|
99
|
+
return self.get_os() == 'Darwin' or sys.platform == "darwin"
|
|
99
100
|
|
|
100
101
|
def is_windows(self) -> bool:
|
|
101
102
|
"""
|
|
@@ -103,7 +104,7 @@ class Platforms:
|
|
|
103
104
|
|
|
104
105
|
:return: True if OS is Windows
|
|
105
106
|
"""
|
|
106
|
-
return self.get_os() == 'Windows'
|
|
107
|
+
return self.get_os() == 'Windows' or sys.platform.startswith("win")
|
|
107
108
|
|
|
108
109
|
def is_snap(self) -> bool:
|
|
109
110
|
"""
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
|
|
@@ -27,10 +27,7 @@ class Helpers:
|
|
|
27
27
|
:param text: text to format
|
|
28
28
|
:return: formatted text
|
|
29
29
|
"""
|
|
30
|
-
|
|
31
|
-
#text = text.replace("#~###~", "~###~") # fix for #~###~ in text (previous versions)
|
|
32
|
-
#text = text.replace("# ~###~", "~###~") # fix for # ~###~ in text (previous versions)
|
|
33
|
-
return text
|
|
30
|
+
return text.strip()
|
|
34
31
|
|
|
35
32
|
def post_format_text(self, text: str) -> str:
|
|
36
33
|
"""
|
|
@@ -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.08.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from datetime import datetime
|
|
@@ -178,11 +178,9 @@ class Renderer(BaseRenderer):
|
|
|
178
178
|
if clear:
|
|
179
179
|
self.clear_output(meta)
|
|
180
180
|
|
|
181
|
-
i
|
|
182
|
-
for item in items:
|
|
181
|
+
for i, item in enumerate(items):
|
|
183
182
|
item.idx = i
|
|
184
183
|
self.append_context_item(meta, item)
|
|
185
|
-
i += 1
|
|
186
184
|
|
|
187
185
|
def append_input(
|
|
188
186
|
self,
|
|
@@ -251,20 +249,23 @@ class Renderer(BaseRenderer):
|
|
|
251
249
|
:param item: context item
|
|
252
250
|
:param footer: True if it is a footer
|
|
253
251
|
"""
|
|
254
|
-
appended =
|
|
252
|
+
appended = set()
|
|
255
253
|
pid = self.get_or_create_pid(meta)
|
|
256
254
|
|
|
257
255
|
# images
|
|
258
256
|
c = len(item.images)
|
|
259
257
|
if c > 0:
|
|
260
258
|
n = 1
|
|
259
|
+
pd = self.pids[pid]
|
|
260
|
+
already = set(pd.images_appended)
|
|
261
261
|
for image in item.images:
|
|
262
|
-
if image in appended or image in
|
|
262
|
+
if image in appended or image in already:
|
|
263
263
|
continue
|
|
264
264
|
try:
|
|
265
|
-
appended.
|
|
265
|
+
appended.add(image)
|
|
266
266
|
self.append_raw(meta, item, self.body.get_image_html(image, n, c))
|
|
267
|
-
|
|
267
|
+
pd.images_appended.append(image)
|
|
268
|
+
already.add(image)
|
|
268
269
|
n += 1
|
|
269
270
|
except Exception as e:
|
|
270
271
|
pass
|
|
@@ -277,7 +278,7 @@ class Renderer(BaseRenderer):
|
|
|
277
278
|
if file in appended:
|
|
278
279
|
continue
|
|
279
280
|
try:
|
|
280
|
-
appended.
|
|
281
|
+
appended.add(file)
|
|
281
282
|
self.append_raw(meta, item, self.body.get_file_html(file, n, c))
|
|
282
283
|
n += 1
|
|
283
284
|
except Exception as e:
|
|
@@ -288,13 +289,16 @@ class Renderer(BaseRenderer):
|
|
|
288
289
|
if c > 0:
|
|
289
290
|
urls_str = []
|
|
290
291
|
n = 1
|
|
292
|
+
pd = self.pids[pid]
|
|
293
|
+
already = set(pd.urls_appended)
|
|
291
294
|
for url in item.urls:
|
|
292
|
-
if url in appended or url in
|
|
295
|
+
if url in appended or url in already:
|
|
293
296
|
continue
|
|
294
297
|
try:
|
|
295
|
-
appended.
|
|
298
|
+
appended.add(url)
|
|
296
299
|
urls_str.append(self.body.get_url_html(url, n, c))
|
|
297
|
-
|
|
300
|
+
pd.urls_appended.append(url)
|
|
301
|
+
already.add(url)
|
|
298
302
|
n += 1
|
|
299
303
|
except Exception as e:
|
|
300
304
|
pass
|
|
@@ -337,8 +341,9 @@ class Renderer(BaseRenderer):
|
|
|
337
341
|
raw_chunk = str(text_chunk)
|
|
338
342
|
|
|
339
343
|
if begin:
|
|
340
|
-
self.pids[pid]
|
|
341
|
-
|
|
344
|
+
pd = self.pids[pid]
|
|
345
|
+
pd.buffer = ""
|
|
346
|
+
pd.is_cmd = False
|
|
342
347
|
|
|
343
348
|
if self.is_timestamp_enabled() and item.output_timestamp is not None:
|
|
344
349
|
name = ""
|
|
@@ -386,13 +391,12 @@ class Renderer(BaseRenderer):
|
|
|
386
391
|
:param text: text to append
|
|
387
392
|
"""
|
|
388
393
|
node = self.get_output_node(meta)
|
|
389
|
-
|
|
390
|
-
if prev_text != "":
|
|
391
|
-
prev_text += "\n\n"
|
|
392
|
-
new_text = f"{prev_text}{text.strip()}"
|
|
393
|
-
node.setPlainText(new_text)
|
|
394
|
-
cur = node.textCursor() # Move cursor to end of text
|
|
394
|
+
cur = node.textCursor()
|
|
395
395
|
cur.movePosition(QTextCursor.End)
|
|
396
|
+
if not node.document().isEmpty():
|
|
397
|
+
cur.insertText("\n\n")
|
|
398
|
+
cur.insertText(text.strip())
|
|
399
|
+
node.setTextCursor(cur)
|
|
396
400
|
|
|
397
401
|
def append_chunk_start(self, meta: CtxMeta, ctx: CtxItem):
|
|
398
402
|
"""
|
|
@@ -437,14 +441,9 @@ class Renderer(BaseRenderer):
|
|
|
437
441
|
:param end: end of the line character
|
|
438
442
|
"""
|
|
439
443
|
node = self.get_output_node(meta)
|
|
440
|
-
cur = node.textCursor()
|
|
444
|
+
cur = node.textCursor()
|
|
441
445
|
cur.movePosition(QTextCursor.End)
|
|
442
|
-
|
|
443
|
-
while s:
|
|
444
|
-
head, sep, s = s.partition("\n") # Split line at LF
|
|
445
|
-
cur.insertText(head)
|
|
446
|
-
if sep:
|
|
447
|
-
cur.insertText("\n")
|
|
446
|
+
cur.insertText(f"{str(text)}{end}")
|
|
448
447
|
node.setTextCursor(cur)
|
|
449
448
|
|
|
450
449
|
def append_timestamp(
|
|
@@ -549,9 +548,6 @@ class Renderer(BaseRenderer):
|
|
|
549
548
|
for node in self.get_all_nodes():
|
|
550
549
|
try:
|
|
551
550
|
node.clear()
|
|
552
|
-
node.document().setMarkdown("")
|
|
553
|
-
node.document().setHtml("")
|
|
554
|
-
node.setPlainText("")
|
|
555
551
|
except Exception as e:
|
|
556
552
|
pass
|
|
557
553
|
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
@@ -15,7 +15,10 @@ import os
|
|
|
15
15
|
import shutil
|
|
16
16
|
from typing import Optional, Dict, Any, List
|
|
17
17
|
|
|
18
|
+
from PySide6.QtWidgets import QApplication
|
|
19
|
+
|
|
18
20
|
from pygpt_net.core.events import RenderEvent
|
|
21
|
+
from pygpt_net.utils import trans
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
class Settings:
|
|
@@ -116,7 +119,7 @@ class Settings:
|
|
|
116
119
|
"""Load defaults from file"""
|
|
117
120
|
file = self.window.ui.dialog['config.editor'].file
|
|
118
121
|
self.load_editor(file)
|
|
119
|
-
self.window.update_status("Restored from user file: {}"
|
|
122
|
+
self.window.update_status(f"Restored from user file: {file}")
|
|
120
123
|
|
|
121
124
|
def load_default_editor_app(self):
|
|
122
125
|
"""Load defaults from file (app)"""
|
|
@@ -125,11 +128,11 @@ class Settings:
|
|
|
125
128
|
if basename.endswith(".css"):
|
|
126
129
|
path = str(os.path.join(self.window.core.config.get_app_path(), "data", "css", basename))
|
|
127
130
|
self.load_editor(file, path)
|
|
128
|
-
self.window.update_status("Restored from app defaults: {}"
|
|
131
|
+
self.window.update_status(f"Restored from app defaults: {basename}")
|
|
129
132
|
elif basename.endswith(".json"):
|
|
130
133
|
path = str(os.path.join(self.window.core.config.get_app_path(), "data", "config", basename))
|
|
131
134
|
self.load_editor(file, path)
|
|
132
|
-
self.window.update_status("Restored from app defaults: {}"
|
|
135
|
+
self.window.update_status(f"Restored from app defaults: {basename}")
|
|
133
136
|
|
|
134
137
|
def load_editor(
|
|
135
138
|
self,
|
|
@@ -151,13 +154,14 @@ class Settings:
|
|
|
151
154
|
self.window.ui.paths['config'].setText(path)
|
|
152
155
|
|
|
153
156
|
self.window.ui.dialog['config.editor'].file = file
|
|
157
|
+
|
|
154
158
|
try:
|
|
155
159
|
with open(path, 'r', encoding="utf-8") as f:
|
|
156
160
|
txt = f.read()
|
|
157
161
|
self.window.ui.editor['config'].setPlainText(txt)
|
|
158
162
|
except Exception as e:
|
|
159
163
|
self.window.core.debug.log(e)
|
|
160
|
-
self.window.update_status("Error loading file: {}"
|
|
164
|
+
self.window.update_status(f"Error loading file: {e}")
|
|
161
165
|
|
|
162
166
|
def save_editor(self):
|
|
163
167
|
"""Save file to disk"""
|
|
@@ -170,8 +174,8 @@ class Settings:
|
|
|
170
174
|
try:
|
|
171
175
|
json.loads(data)
|
|
172
176
|
except Exception as e:
|
|
173
|
-
self.window.update_status("This is not a valid JSON: {}"
|
|
174
|
-
self.window.ui.dialogs.alert("This is not a valid JSON: {}"
|
|
177
|
+
self.window.update_status(f"This is not a valid JSON: {e}")
|
|
178
|
+
self.window.ui.dialogs.alert(f"This is not a valid JSON: {e}")
|
|
175
179
|
return
|
|
176
180
|
path = os.path.join(self.window.core.config.get_user_path(), file)
|
|
177
181
|
elif file.endswith('.css'):
|
|
@@ -189,22 +193,48 @@ class Settings:
|
|
|
189
193
|
backup_path = os.path.join(self.window.core.config.get_user_path(), backup_file)
|
|
190
194
|
if os.path.isfile(path):
|
|
191
195
|
shutil.copyfile(path, backup_path)
|
|
192
|
-
self.window.update_status("Created backup file: {}"
|
|
196
|
+
self.window.update_status(f"Created backup file: {backup_file}")
|
|
197
|
+
|
|
198
|
+
prev_content = None
|
|
199
|
+
if os.path.isfile(path):
|
|
200
|
+
try:
|
|
201
|
+
with open(path, 'r', encoding="utf-8") as f:
|
|
202
|
+
prev_content = f.read()
|
|
203
|
+
except Exception as e:
|
|
204
|
+
pass
|
|
193
205
|
|
|
194
206
|
# save changes to current file
|
|
195
207
|
try:
|
|
196
208
|
with open(path, 'w', encoding="utf-8") as f:
|
|
197
209
|
f.write(data)
|
|
198
|
-
self.window.update_status("Saved file: {}"
|
|
199
|
-
|
|
210
|
+
self.window.update_status(f"Saved file: {path}")
|
|
211
|
+
except Exception as e:
|
|
212
|
+
self.window.core.debug.log(e)
|
|
213
|
+
self.window.update_status(f"Error saving file: {path}")
|
|
214
|
+
return # abort if error
|
|
215
|
+
|
|
216
|
+
if prev_content == data:
|
|
217
|
+
self.window.update_status(f"Saved file: {path}")
|
|
218
|
+
self.window.ui.dialogs.alert(f"Saved file: {path}")
|
|
219
|
+
return # no changes made, no need to reload
|
|
220
|
+
|
|
221
|
+
if file in ("config.json", "models.json") or file.endswith('.css'):
|
|
222
|
+
self.window.update_status(trans("status.reloading"))
|
|
223
|
+
|
|
224
|
+
QApplication.processEvents() # process events to update UI
|
|
225
|
+
|
|
226
|
+
try:
|
|
200
227
|
if file == "config.json":
|
|
201
228
|
self.window.core.config.load_config() # reload config
|
|
202
229
|
elif file == "models.json":
|
|
203
230
|
self.window.core.models.load() # reload models
|
|
204
231
|
elif file.endswith('.css'):
|
|
205
|
-
|
|
206
|
-
self.window.dispatch(event)
|
|
232
|
+
self.window.dispatch(RenderEvent(RenderEvent.ON_THEME_CHANGE))
|
|
207
233
|
self.window.controller.theme.reload(force=True) # reload theme
|
|
234
|
+
self.window.update_status(f"Saved file: {path}")
|
|
235
|
+
self.window.ui.dialogs.alert(f"Saved file: {path}")
|
|
208
236
|
except Exception as e:
|
|
209
237
|
self.window.core.debug.log(e)
|
|
210
|
-
self.window.update_status("Error
|
|
238
|
+
self.window.update_status(f"Error reloading saved file: {path}")
|
|
239
|
+
|
|
240
|
+
|