pygpt-net 2.6.22__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.
Files changed (38) hide show
  1. pygpt_net/CHANGELOG.txt +8 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/controller/agent/llama.py +3 -0
  4. pygpt_net/controller/chat/response.py +4 -0
  5. pygpt_net/controller/files/files.py +24 -55
  6. pygpt_net/controller/theme/theme.py +3 -3
  7. pygpt_net/core/agents/observer/evaluation.py +2 -2
  8. pygpt_net/core/agents/runners/loop.py +1 -0
  9. pygpt_net/core/bridge/bridge.py +2 -0
  10. pygpt_net/core/filesystem/opener.py +261 -0
  11. pygpt_net/core/filesystem/url.py +13 -10
  12. pygpt_net/core/platforms/platforms.py +5 -4
  13. pygpt_net/data/config/config.json +2 -2
  14. pygpt_net/data/config/models.json +2 -2
  15. pygpt_net/data/css/web-blocks.dark.css +7 -1
  16. pygpt_net/data/css/web-blocks.light.css +5 -2
  17. pygpt_net/data/css/web-chatgpt.dark.css +7 -1
  18. pygpt_net/data/css/web-chatgpt.light.css +3 -0
  19. pygpt_net/data/css/web-chatgpt_wide.dark.css +7 -1
  20. pygpt_net/data/css/web-chatgpt_wide.light.css +3 -0
  21. pygpt_net/data/locale/locale.de.ini +1 -0
  22. pygpt_net/data/locale/locale.en.ini +1 -0
  23. pygpt_net/data/locale/locale.es.ini +1 -0
  24. pygpt_net/data/locale/locale.fr.ini +1 -0
  25. pygpt_net/data/locale/locale.it.ini +1 -0
  26. pygpt_net/data/locale/locale.pl.ini +1 -0
  27. pygpt_net/data/locale/locale.uk.ini +1 -0
  28. pygpt_net/data/locale/locale.zh.ini +1 -0
  29. pygpt_net/provider/core/config/patch.py +12 -1
  30. pygpt_net/ui/layout/toolbox/agent_llama.py +2 -3
  31. pygpt_net/ui/widget/tabs/layout.py +6 -4
  32. pygpt_net/ui/widget/tabs/output.py +348 -13
  33. pygpt_net/ui/widget/textarea/input.py +74 -8
  34. {pygpt_net-2.6.22.dist-info → pygpt_net-2.6.23.dist-info}/METADATA +25 -25
  35. {pygpt_net-2.6.22.dist-info → pygpt_net-2.6.23.dist-info}/RECORD +38 -37
  36. {pygpt_net-2.6.22.dist-info → pygpt_net-2.6.23.dist-info}/LICENSE +0 -0
  37. {pygpt_net-2.6.22.dist-info → pygpt_net-2.6.23.dist-info}/WHEEL +0 -0
  38. {pygpt_net-2.6.22.dist-info → pygpt_net-2.6.23.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,11 @@
1
+ 2.6.23 (2025-08-25)
2
+
3
+ - Added an inline "Add a new chat" button to the right of the tabs.
4
+ - Added an "Add Attachment" button in the input field.
5
+ - Improved file open in the system's file manager
6
+ - Fixed the restoration of input text color when changing themes from light to dark.
7
+ - Fixed last eval step finish if 100% complete.
8
+
1
9
  2.6.22 (2025-08-25)
2
10
 
3
11
  - UI refactor and optimizations.
pygpt_net/__init__.py CHANGED
@@ -13,7 +13,7 @@ __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.22"
16
+ __version__ = "2.6.23"
17
17
  __build__ = "2025-08-25"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
@@ -104,6 +104,9 @@ class Llama:
104
104
 
105
105
  def on_end(self):
106
106
  """End of run"""
107
+ self.window.dispatch(KernelEvent(KernelEvent.STATE_IDLE, {
108
+ "id": "agent",
109
+ }))
107
110
  self.eval_step = 0 # reset evaluation step
108
111
  if self.window.core.config.get("agent.goal.notify"):
109
112
  # show notification if enabled and mode is not llama_index
@@ -269,6 +269,10 @@ class Response:
269
269
  self.window.update_status(trans("status.agent.reasoning"))
270
270
  controller.chat.common.lock_input() # lock input, re-enable stop button
271
271
 
272
+ if ctx.extra is not None and (isinstance(ctx.extra, dict) and "agent_eval_finish" in ctx.extra):
273
+ controller.agent.llama.on_end(ctx)
274
+ return
275
+
272
276
  # agent final response
273
277
  if ctx.extra is not None and (isinstance(ctx.extra, dict) and "agent_finish" in ctx.extra):
274
278
  controller.agent.llama.on_finish(ctx) # evaluate response and continue if needed
@@ -6,21 +6,18 @@
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.23 15:00:00 #
9
+ # Updated Date: 2025.08.25 18:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
13
13
  import os
14
14
  import shutil
15
15
  from typing import Optional
16
+ from shutil import copy2
16
17
 
17
- from PySide6.QtCore import QUrl
18
- from PySide6.QtGui import QDesktopServices
19
18
  from PySide6.QtWidgets import QFileDialog, QApplication
20
- from showinfm import show_in_file_manager
21
- from shutil import copy2
22
- import subprocess
23
19
 
20
+ from pygpt_net.core.filesystem.opener import Opener
24
21
  from pygpt_net.utils import trans
25
22
 
26
23
 
@@ -60,12 +57,12 @@ class Files:
60
57
  try:
61
58
  shutil.rmtree(path) # delete directory with all files
62
59
  self.window.update_status(
63
- "[OK] Deleted directory: {}".format(os.path.basename(path))
60
+ f"[OK] Deleted directory: {os.path.basename(path)}"
64
61
  )
65
62
  self.update_explorer()
66
63
  except Exception as e:
67
64
  self.window.core.debug.log(e)
68
- print("Error deleting directory: {} - {}".format(path, e))
65
+ print(f"Error deleting directory: {path} - {e}")
69
66
 
70
67
  def touch_file(
71
68
  self,
@@ -93,12 +90,12 @@ class Files:
93
90
  filepath = os.path.join(path, name)
94
91
  open(filepath, 'a').close()
95
92
  self.window.update_status(
96
- "[OK] Created file: {}".format(os.path.basename(filepath))
93
+ f"[OK] Created file: {filepath}"
97
94
  )
98
95
  self.update_explorer()
99
96
  except Exception as e:
100
97
  self.window.core.debug.log(e)
101
- print("Error creating file: {} - {}".format(path, e))
98
+ print(f"Error creating file: {path} - {e}")
102
99
 
103
100
  def delete(
104
101
  self,
@@ -130,12 +127,12 @@ class Files:
130
127
  try:
131
128
  os.remove(path)
132
129
  self.window.update_status(
133
- "[OK] Deleted file: {}".format(os.path.basename(path))
130
+ f"[OK] Deleted file: {os.path.basename(path)}"
134
131
  )
135
132
  self.update_explorer()
136
133
  except Exception as e:
137
134
  self.window.core.debug.log(e)
138
- print("Error deleting file: {} - {}".format(path, e))
135
+ print(f"Error deleting file: {path} - {e}")
139
136
 
140
137
  def duplicate_local(
141
138
  self,
@@ -172,7 +169,7 @@ class Files:
172
169
 
173
170
  if os.path.exists(new_path):
174
171
  self.window.update_status(
175
- "[ERROR] File already exists: {}".format(os.path.basename(new_path))
172
+ f"[ERROR] File already exists: {os.path.basename(new_path)}"
176
173
  )
177
174
  return
178
175
 
@@ -181,12 +178,12 @@ class Files:
181
178
  else:
182
179
  copy2(path, new_path)
183
180
  self.window.update_status(
184
- "[OK] Duplicated file: {} -> {}".format(os.path.basename(path), name)
181
+ f"[OK] Duplicated file: {os.path.basename(path)} -> {name}"
185
182
  )
186
183
  self.update_explorer()
187
184
  except Exception as e:
188
185
  self.window.core.debug.log(e)
189
- print("Error duplicating file: {} - {}".format(path, e))
186
+ print(f"Error duplicating file: {path} - {e}")
190
187
 
191
188
  def download_local(self, path: str):
192
189
  """
@@ -211,11 +208,11 @@ class Files:
211
208
  else:
212
209
  shutil.copy2(path, files[0])
213
210
  self.window.update_status(
214
- "[OK] Downloaded file: {}".format(os.path.basename(path))
211
+ f"[OK] Downloaded file: {os.path.basename(path)}"
215
212
  )
216
213
  except Exception as e:
217
214
  self.window.core.debug.log(e)
218
- print("Error downloading file: {} - {}".format(path, e))
215
+ print(f"Error downloading file: {path} - {e}")
219
216
 
220
217
  def upload_local(
221
218
  self,
@@ -268,9 +265,9 @@ class Files:
268
265
  num += 1
269
266
  except Exception as e:
270
267
  self.window.core.debug.log(e)
271
- print("Error copying file {}: {}".format(file_path, e))
268
+ print(f"Error copying file {file_path}: {e}")
272
269
  if num > 0:
273
- self.window.update_status("[OK] Uploaded: {} files.".format(num))
270
+ self.window.update_status(f"[OK] Uploaded: {num} files.")
274
271
  self.update_explorer()
275
272
 
276
273
  def rename(self, path: str):
@@ -299,12 +296,12 @@ class Files:
299
296
  new_path = os.path.join(os.path.dirname(path), name)
300
297
  if os.path.exists(new_path):
301
298
  self.window.update_status(
302
- "[ERROR] File already exists: {}".format(os.path.basename(new_path))
299
+ f"[ERROR] File already exists: {os.path.basename(new_path)}"
303
300
  )
304
301
  return
305
302
  os.rename(path, new_path)
306
303
  self.window.update_status(
307
- "[OK] Renamed: {} -> {}".format(os.path.basename(path), name)
304
+ f"[OK] Renamed: {os.path.basename(path)} -> {name}"
308
305
  )
309
306
  self.window.ui.dialog['rename'].close()
310
307
  self.update_explorer()
@@ -328,24 +325,8 @@ class Files:
328
325
 
329
326
  :param path: path to file or directory
330
327
  """
331
- if os.path.isdir(path):
332
- if not self.window.core.platforms.is_snap():
333
- url = QUrl.fromLocalFile(path)
334
- QDesktopServices.openUrl(url)
335
- else:
336
- url = QUrl.fromLocalFile(path)
337
- if not QDesktopServices.openUrl(url):
338
- subprocess.run(['gio', 'open', path])
339
- else:
340
- if not self.window.core.platforms.is_snap():
341
- path = self.window.core.filesystem.get_path(path)
342
- url = QUrl.fromLocalFile(path)
343
- QDesktopServices.openUrl(url)
344
- else:
345
- path = self.window.core.filesystem.get_path(path)
346
- url = QUrl.fromLocalFile(path)
347
- QDesktopServices.openUrl(url)
348
- #subprocess.run(['gio', 'open', path])
328
+ path = self.window.core.filesystem.get_path(path)
329
+ Opener.open_path(path, reveal=False)
349
330
 
350
331
  def open_in_file_manager(
351
332
  self,
@@ -359,20 +340,8 @@ class Files:
359
340
  :param select: select file in file manager
360
341
  """
361
342
  path = self.window.core.filesystem.get_path(path)
362
- if select: # path to file: open containing directory
363
- path = self.window.core.filesystem.get_path(
364
- os.path.dirname(path)
365
- )
366
-
367
343
  if os.path.exists(path):
368
- if not self.window.core.platforms.is_snap():
369
- url = self.window.core.filesystem.get_url(path)
370
- QDesktopServices.openUrl(url)
371
- # show_in_file_manager(path, select)
372
- else:
373
- url = QUrl.fromLocalFile(path)
374
- if not QDesktopServices.openUrl(url):
375
- subprocess.run(['gio', 'open', path])
344
+ Opener.open_path(path, reveal=select)
376
345
 
377
346
  def make_dir_dialog(self, path: str):
378
347
  """
@@ -411,7 +380,7 @@ class Files:
411
380
  return
412
381
  os.makedirs(path_dir, exist_ok=True)
413
382
  self.window.update_status(
414
- "[OK] Directory created: {}".format(name)
383
+ f"[OK] Directory created: {name}"
415
384
  )
416
385
  self.update_explorer()
417
386
 
@@ -473,9 +442,9 @@ class Files:
473
442
  :param path: path to file
474
443
  """
475
444
  if os.path.isdir(path):
476
- cmd = "Please list files from directory: {}".format(self.strip_work_path(path))
445
+ cmd = f"Please list files from directory: {self.strip_work_path(path)}"
477
446
  else:
478
- cmd = "Please read this file from current directory: {}".format(self.strip_work_path(path))
447
+ cmd = f"Please read this file from current directory: {self.strip_work_path(path)}"
479
448
  self.window.controller.chat.common.append_to_input(cmd)
480
449
 
481
450
  def make_ts_prefix(self) -> str:
@@ -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.24 23:00:00 #
9
+ # Updated Date: 2025.08.25 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -50,7 +50,7 @@ class Theme:
50
50
 
51
51
  :param name: theme name
52
52
  """
53
- self.window.update_status(trans("status.loading"))
53
+ self.window.update_status(trans("status.reloading"))
54
54
  QApplication.processEvents()
55
55
  self.toggle(name, force=True)
56
56
  self.window.update_status("")
@@ -62,7 +62,7 @@ class Theme:
62
62
  :param name: option name
63
63
  :param value: option value
64
64
  """
65
- self.window.update_status(trans("status.loading"))
65
+ self.window.update_status(trans("status.reloading"))
66
66
  QApplication.processEvents()
67
67
  self.toggle_option(name, value)
68
68
  self.window.update_status("")
@@ -212,7 +212,7 @@ class Evaluation:
212
212
  return prompt.format(
213
213
  task=main_task,
214
214
  input=last_input,
215
- output=final_response
215
+ output=final_response,
216
216
  )
217
217
 
218
218
  def get_prompt_complete(self, history: List[CtxItem]) -> str:
@@ -229,7 +229,7 @@ class Evaluation:
229
229
  return prompt.format(
230
230
  task=main_task,
231
231
  input=last_input,
232
- output=final_response
232
+ output=final_response,
233
233
  )
234
234
 
235
235
  def get_tools(self) -> List[FunctionTool]:
@@ -145,6 +145,7 @@ class Loop(BaseRunner):
145
145
  score_label=trans('eval.score'),
146
146
  score=str(score)
147
147
  )
148
+ ctx.extra["agent_eval_finish"] = True
148
149
  self.send_response(ctx, signals, KernelEvent.APPEND_END, msg=msg)
149
150
  self.set_idle(signals)
150
151
  return True
@@ -150,6 +150,7 @@ class Bridge:
150
150
  # async call
151
151
  self.window.core.debug.info("[bridge] Starting worker (async)...")
152
152
  self.window.threadpool.start(worker)
153
+ self.worker = worker
153
154
  return True
154
155
 
155
156
  def request_next(
@@ -177,6 +178,7 @@ class Bridge:
177
178
 
178
179
  # async call
179
180
  self.window.threadpool.start(worker)
181
+ self.worker = worker
180
182
  return True
181
183
 
182
184
  def call(
@@ -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)
@@ -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
- extra_schemes = [
31
- 'extra-audio-read',
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.07.18 17:00:00 #
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
  """
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.22",
4
- "app.version": "2.6.22",
3
+ "version": "2.6.23",
4
+ "app.version": "2.6.23",
5
5
  "updated_at": "2025-08-25T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.22",
4
- "app.version": "2.6.22",
3
+ "version": "2.6.23",
4
+ "app.version": "2.6.23",
5
5
  "updated_at": "2025-08-25T23:07:35"
6
6
  },
7
7
  "items": {