pygpt-net 2.5.98.post1__py3-none-any.whl → 2.6.0.post1__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 (47) hide show
  1. pygpt_net/CHANGELOG.txt +9 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/ctx/ctx.py +7 -2
  4. pygpt_net/core/agents/runners/openai_workflow.py +9 -6
  5. pygpt_net/core/render/plain/pid.py +3 -2
  6. pygpt_net/core/render/web/body.py +21 -5
  7. pygpt_net/core/render/web/pid.py +26 -6
  8. pygpt_net/core/render/web/renderer.py +4 -10
  9. pygpt_net/data/config/config.json +3 -3
  10. pygpt_net/data/config/models.json +3 -3
  11. pygpt_net/data/css/style.dark.css +13 -0
  12. pygpt_net/data/css/style.light.css +29 -0
  13. pygpt_net/data/icon.ico +0 -0
  14. pygpt_net/data/icon.png +0 -0
  15. pygpt_net/data/locale/locale.de.ini +1 -1
  16. pygpt_net/data/locale/locale.en.ini +1 -1
  17. pygpt_net/data/locale/locale.es.ini +1 -1
  18. pygpt_net/data/locale/locale.fr.ini +1 -1
  19. pygpt_net/data/locale/locale.it.ini +1 -1
  20. pygpt_net/data/locale/locale.pl.ini +1 -1
  21. pygpt_net/data/locale/locale.uk.ini +1 -1
  22. pygpt_net/data/locale/locale.zh.ini +1 -1
  23. pygpt_net/provider/agents/llama_index/code_act.py +5 -4
  24. pygpt_net/provider/agents/llama_index/openai.py +3 -3
  25. pygpt_net/provider/agents/llama_index/openai_assistant.py +3 -3
  26. pygpt_net/provider/agents/llama_index/planner.py +6 -6
  27. pygpt_net/provider/agents/llama_index/react.py +3 -7
  28. pygpt_net/provider/agents/llama_index/react_workflow.py +4 -7
  29. pygpt_net/provider/agents/openai/agent_b2b.py +45 -10
  30. pygpt_net/provider/agents/openai/agent_planner.py +24 -2
  31. pygpt_net/provider/agents/openai/agent_with_experts.py +1 -38
  32. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +23 -38
  33. pygpt_net/provider/agents/openai/agent_with_feedback.py +23 -2
  34. pygpt_net/provider/agents/openai/bot_researcher.py +2 -6
  35. pygpt_net/provider/agents/openai/evolve.py +23 -2
  36. pygpt_net/provider/core/config/patch.py +7 -0
  37. pygpt_net/provider/gpt/__init__.py +21 -3
  38. pygpt_net/tools/html_canvas/ui/widgets.py +4 -0
  39. pygpt_net/tools/media_player/tool.py +11 -2
  40. pygpt_net/tools/media_player/ui/widgets.py +99 -94
  41. pygpt_net/ui/widget/calendar/select.py +10 -2
  42. pygpt_net/ui/widget/filesystem/explorer.py +1 -0
  43. {pygpt_net-2.5.98.post1.dist-info → pygpt_net-2.6.0.post1.dist-info}/METADATA +14 -147
  44. {pygpt_net-2.5.98.post1.dist-info → pygpt_net-2.6.0.post1.dist-info}/RECORD +47 -47
  45. {pygpt_net-2.5.98.post1.dist-info → pygpt_net-2.6.0.post1.dist-info}/LICENSE +0 -0
  46. {pygpt_net-2.5.98.post1.dist-info → pygpt_net-2.6.0.post1.dist-info}/WHEEL +0 -0
  47. {pygpt_net-2.5.98.post1.dist-info → pygpt_net-2.6.0.post1.dist-info}/entry_points.txt +0 -0
@@ -6,15 +6,13 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.03.26 15:00:00 #
9
+ # Updated Date: 2025.08.12 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
13
13
 
14
14
  from PySide6.QtCore import Qt, QUrl, QTimer
15
15
  from PySide6.QtGui import QIcon, QAction
16
- from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
17
- from PySide6.QtMultimediaWidgets import QVideoWidget
18
16
  from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QVBoxLayout,
19
17
  QFileDialog, QSlider, QLabel, QStyle, QSizePolicy, QMenu, QMessageBox)
20
18
 
@@ -27,7 +25,12 @@ class VideoPlayerWidget(QWidget):
27
25
  def __init__(self, window=None):
28
26
  super().__init__(window)
29
27
  self.window = window
30
- self.player = QMediaPlayer(window)
28
+ self.player = None
29
+ self.audio = None
30
+ self.video = None
31
+ self._QMediaPlayer = None
32
+ self._QAudioOutput = None
33
+ self._QVideoWidget = None
31
34
  self.loaded = False
32
35
  self.path = None
33
36
  self.stopped = False
@@ -41,10 +44,9 @@ class VideoPlayerWidget(QWidget):
41
44
  self.autoplay_timer.setSingleShot(True)
42
45
  self.autoplay_timer.timeout.connect(self.after_loaded)
43
46
 
44
- self.audio = QAudioOutput()
45
- self.video = QVideoWidget(window)
46
- self.video.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
47
- self.video.setMinimumSize(640, 480)
47
+ self._video_placeholder = QWidget(window)
48
+ self._video_placeholder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
49
+ self._video_placeholder.setMinimumSize(640, 480)
48
50
 
49
51
  self.btn_play_pause = QPushButton()
50
52
  self.btn_play_pause.setEnabled(False)
@@ -81,7 +83,6 @@ class VideoPlayerWidget(QWidget):
81
83
  layout_bottom.addStretch()
82
84
  layout_bottom.addLayout(layout_volume)
83
85
 
84
- # layout
85
86
  layout_controls = QHBoxLayout()
86
87
  layout_controls.addWidget(self.btn_open)
87
88
  layout_controls.addWidget(self.btn_play_pause)
@@ -93,10 +94,11 @@ class VideoPlayerWidget(QWidget):
93
94
  layout_slider.addWidget(self.label_duration)
94
95
 
95
96
  layout = QVBoxLayout()
96
- layout.addWidget(self.video)
97
+ layout.addWidget(self._video_placeholder)
97
98
  layout.addLayout(layout_slider)
98
99
  layout.addLayout(layout_controls)
99
100
  layout.addLayout(layout_bottom)
101
+ self._layout_main = layout
100
102
 
101
103
  self.setLayout(layout)
102
104
  self.setContextMenuPolicy(Qt.CustomContextMenu)
@@ -105,33 +107,59 @@ class VideoPlayerWidget(QWidget):
105
107
  self.update_play_pause_icon()
106
108
  self.bind_signals()
107
109
 
108
- def bind_signals(self):
109
- """Bind signals"""
110
- self.btn_open.clicked.connect(self.open_file)
111
- self.btn_play_pause.clicked.connect(self.toggle_play_pause)
112
- self.btn_stop.clicked.connect(self.stop_video)
113
- self.btn_mute.toggled.connect(self.set_muted)
110
+ def _ensure_multimedia(self):
111
+ """Ensure multimedia components are loaded"""
112
+ if self.player is not None:
113
+ return
114
+ from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
115
+ from PySide6.QtMultimediaWidgets import QVideoWidget
116
+ self._QMediaPlayer = QMediaPlayer
117
+ self._QAudioOutput = QAudioOutput
118
+ self._QVideoWidget = QVideoWidget
119
+ self.player = QMediaPlayer(self.window)
120
+ self.audio = QAudioOutput()
121
+ self.player.setAudioOutput(self.audio)
122
+ self.video = QVideoWidget(self.window)
123
+ self.video.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
124
+ self.video.setMinimumSize(640, 480)
125
+ idx = self._layout_main.indexOf(self._video_placeholder)
126
+ if idx != -1:
127
+ self._layout_main.removeWidget(self._video_placeholder)
128
+ self._video_placeholder.setParent(None)
129
+ self._layout_main.insertWidget(idx, self.video)
130
+ else:
131
+ self._layout_main.insertWidget(0, self.video)
132
+ self.video.mousePressEvent = self.video_widget_clicked
133
+ self.player.setVideoOutput(self.video)
114
134
  self.player.positionChanged.connect(self.position_changed)
115
135
  self.player.durationChanged.connect(self.duration_changed)
116
136
  self.player.playbackStateChanged.connect(self.on_playback_state_changed)
117
137
  self.player.mediaStatusChanged.connect(self.media_status_changed)
118
- self.video.mousePressEvent = self.video_widget_clicked
138
+ self.update_mute_icon()
139
+ self.update_volume_slider()
140
+ self.adjust_volume(self.volume_slider.value())
141
+ self.set_muted(self.btn_mute.isChecked())
119
142
 
120
- self.player.setAudioOutput(self.audio)
121
- self.player.setVideoOutput(self.video)
143
+ def bind_signals(self):
144
+ """Bind signals to player controls"""
145
+ self.btn_open.clicked.connect(self.open_file)
146
+ self.btn_play_pause.clicked.connect(self.toggle_play_pause)
147
+ self.btn_stop.clicked.connect(self.stop_video)
148
+ self.btn_mute.toggled.connect(self.set_muted)
122
149
  self.volume_slider.valueChanged.connect(self.adjust_volume)
123
150
  self.slider.sliderPressed.connect(self.on_slider_pressed)
124
151
  self.slider.sliderReleased.connect(self.on_slider_released)
152
+ self._video_placeholder.mousePressEvent = self.video_widget_clicked
125
153
  self.seeking = False
126
154
 
127
155
  def update(self):
128
- """Update player"""
156
+ """Update player state"""
129
157
  self.update_mute_icon()
130
158
  self.update_volume_slider()
131
159
  self.update_label_path()
132
160
 
133
161
  def open_file(self):
134
- """Open file"""
162
+ """Open video file dialog"""
135
163
  path, _ = QFileDialog.getOpenFileName(
136
164
  self,
137
165
  trans("action.video.open"),
@@ -152,12 +180,13 @@ class VideoPlayerWidget(QWidget):
152
180
  self.stopped = False
153
181
  self.seeking = False
154
182
  self.reset()
183
+ self._ensure_multimedia()
155
184
  self.player.setSource(QUrl.fromLocalFile(path))
156
185
  self.set_path(path)
157
186
  self.enable()
158
187
  self.autoplay_timer.start()
159
188
  self.update()
160
- self.window.tools.get("player").store_path(path) # save
189
+ self.window.tools.get("player").store_path(path)
161
190
 
162
191
  def enable(self):
163
192
  """Enable player"""
@@ -170,12 +199,13 @@ class VideoPlayerWidget(QWidget):
170
199
  if not self.loaded and self.path:
171
200
  self.open(self.path)
172
201
  else:
202
+ self._ensure_multimedia()
173
203
  self.update_audio()
174
204
  self.player.play()
175
205
 
176
206
  def stop_video(self):
177
207
  """Stop video"""
178
- if self.player.source():
208
+ if self.player and self.player.source():
179
209
  self.player.stop()
180
210
  self.stopped = True
181
211
  self.slider.setValue(0)
@@ -185,16 +215,15 @@ class VideoPlayerWidget(QWidget):
185
215
 
186
216
  def toggle_play_pause(self):
187
217
  """Toggle play/pause"""
188
- if self.player.playbackState() == QMediaPlayer.PlayingState:
218
+ if self.player and self._QMediaPlayer and self.player.playbackState() == self._QMediaPlayer.PlayingState:
189
219
  self.player.pause()
190
220
  else:
191
221
  self.play_video()
192
-
193
222
  self.on_playback_state_changed()
194
223
 
195
224
  def reset(self):
196
225
  """Reset player to default state"""
197
- if self.player.source():
226
+ if self.player and self.player.source():
198
227
  self.player.stop()
199
228
  self.player.setSource(QUrl())
200
229
  self.btn_play_pause.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
@@ -206,7 +235,7 @@ class VideoPlayerWidget(QWidget):
206
235
  def reset_player(self):
207
236
  """Reset player to default state"""
208
237
  if self.player is not None:
209
- if self.player.playbackState() == QMediaPlayer.PlayingState:
238
+ if self._QMediaPlayer and self.player.playbackState() == self._QMediaPlayer.PlayingState:
210
239
  self.player.stop()
211
240
  self.player.setSource(QUrl())
212
241
 
@@ -216,60 +245,23 @@ class VideoPlayerWidget(QWidget):
216
245
 
217
246
  :param point: point
218
247
  """
219
- if not self.path or not self.player.source():
248
+ if not self.path or not (self.player and self.player.source()):
220
249
  return
221
-
222
- # TODO: implement grab screenshot
223
- """
224
- use_actions = []
225
- action_as_attachment = QAction(
226
- QIcon(":/icons/attachment.svg"),
227
- trans('action.use.attachment'),
228
- self,
229
- )
230
- action_as_attachment.triggered.connect(
231
- lambda: self.use_as_attachment(),
232
- )
233
- action_as_image = QAction(
234
- QIcon(":/icons/image.svg"),
235
- trans('action.use.image'),
236
- self,
237
- )
238
- action_as_image.triggered.connect(
239
- lambda: self.use_as_image(),
240
- )
241
- use_actions.append(action_as_image)
242
- use_actions.append(action_as_attachment)
243
-
244
- # use by type
245
- if use_actions:
246
- # use menu
247
- use_menu = QMenu(trans('action.use'), self)
248
- for action in use_actions:
249
- use_menu.addAction(action)
250
- context_menu.addMenu(use_menu)
251
- """
252
250
  context_menu = QMenu(self)
253
251
  save_as_action = QAction(QIcon(":/icons/save.svg"), trans("action.save_as"), self)
254
252
  save_as_action.triggered.connect(
255
253
  lambda: self.window.tools.get("player").save_as_file()
256
254
  )
257
- # full_screen_action = QAction('Fullscreen', self)
258
- # full_screen_action.triggered.connect(self.toggle_fullscreen)
259
255
  context_menu.addAction(save_as_action)
260
-
261
- # context_menu.addAction(full_screen_action)
262
256
  context_menu.exec(self.mapToGlobal(point))
263
257
 
264
258
  def use_as_attachment(self):
265
259
  """Use as attachment"""
266
- # TODO: implement grab screenshot
267
260
  path = self.window.tools.get("player").grab_frame()
268
261
  self.window.controller.files.use_attachment(path)
269
262
 
270
263
  def use_as_image(self):
271
264
  """Use as image"""
272
- # TODO: implement grab screenshot
273
265
  path = self.window.tools.get("player").grab_frame()
274
266
  self.window.controller.painter.open_external(path)
275
267
 
@@ -279,9 +271,10 @@ class VideoPlayerWidget(QWidget):
279
271
 
280
272
  :param value: volume value
281
273
  """
282
- self.window.core.config.set("video.player.volume", value) # save
283
- volume = value / 100.0
284
- self.audio.setVolume(volume)
274
+ self.window.core.config.set("video.player.volume", value)
275
+ if self.audio:
276
+ volume = value / 100.0
277
+ self.audio.setVolume(volume)
285
278
 
286
279
  def set_path(self, path: str):
287
280
  """
@@ -292,7 +285,8 @@ class VideoPlayerWidget(QWidget):
292
285
  self.path = path
293
286
  self.update_label_path()
294
287
  if not self.loaded and self.path:
295
- if not self.player.source():
288
+ if not self.player or not self.player.source():
289
+ self._ensure_multimedia()
296
290
  self.reset()
297
291
  self.player.setSource(QUrl.fromLocalFile(self.path))
298
292
  self.enable()
@@ -305,7 +299,6 @@ class VideoPlayerWidget(QWidget):
305
299
 
306
300
  def toggle_fullscreen(self):
307
301
  """Toggle fullscreen"""
308
- # TODO: implement fullscreen
309
302
  if self.window.ui.dialog['video_player'].isFullScreen():
310
303
  self.window.ui.dialog['video_player'].showNormal()
311
304
  else:
@@ -317,24 +310,27 @@ class VideoPlayerWidget(QWidget):
317
310
 
318
311
  :param status: status
319
312
  """
320
- if status == QMediaPlayer.LoadedMedia:
313
+ if not self._QMediaPlayer:
314
+ return
315
+ if status == self._QMediaPlayer.LoadedMedia:
321
316
  self.loaded = True
322
317
  self.force_resize()
323
- elif status == QMediaPlayer.InvalidMedia:
318
+ elif status == self._QMediaPlayer.InvalidMedia:
324
319
  self.loaded = False
325
320
  self.window.ui.dialogs.alert("Failed to load media file, missing video codec?")
326
321
 
327
322
  def after_loaded(self):
328
323
  """Start playback after loaded"""
329
- if self.player.mediaStatus() == QMediaPlayer.LoadedMedia:
324
+ if self.player and self._QMediaPlayer and self.player.mediaStatus() == self._QMediaPlayer.LoadedMedia:
330
325
  self.loaded = True
331
- if self.player.playbackState() != QMediaPlayer.PlayingState:
326
+ if self.player.playbackState() != self._QMediaPlayer.PlayingState:
332
327
  self.toggle_play_pause()
333
328
  self.force_resize()
334
329
 
335
330
  def force_resize(self):
336
331
  """Force resize fix"""
337
- # fix for video not showing after re-opening the dialog
332
+ if not self.video:
333
+ return
338
334
  self.video.hide()
339
335
  self.video.show()
340
336
  self.video.update()
@@ -377,8 +373,9 @@ class VideoPlayerWidget(QWidget):
377
373
 
378
374
  :param state: muted
379
375
  """
380
- self.audio.setMuted(state)
381
- self.window.core.config.set("video.player.volume.mute", state) # save
376
+ if self.audio:
377
+ self.audio.setMuted(state)
378
+ self.window.core.config.set("video.player.volume.mute", state)
382
379
 
383
380
  def set_position(self, position):
384
381
  """
@@ -386,23 +383,26 @@ class VideoPlayerWidget(QWidget):
386
383
 
387
384
  :param position: position
388
385
  """
389
- if self.player.playbackState() == QMediaPlayer.PlayingState:
386
+ if not self.player:
387
+ return
388
+ if self._QMediaPlayer and self.player.playbackState() == self._QMediaPlayer.PlayingState:
390
389
  self.player.pause()
391
-
392
390
  self.player.positionChanged.connect(self.on_position_set)
393
391
  self.player.setPosition(position)
394
392
 
395
393
  def on_slider_pressed(self):
396
394
  """Slider pressed"""
397
395
  self.seeking = True
398
- self.player.pause()
396
+ if self.player:
397
+ self.player.pause()
399
398
 
400
399
  def on_slider_released(self):
401
400
  """Slider released"""
402
401
  if self.seeking:
403
- self.player.setPosition(self.slider.value())
404
- self.update_audio()
405
- self.player.play()
402
+ if self.player:
403
+ self.player.setPosition(self.slider.value())
404
+ self.update_audio()
405
+ self.player.play()
406
406
  self.seeking = False
407
407
 
408
408
  def position_changed(self, position):
@@ -421,9 +421,12 @@ class VideoPlayerWidget(QWidget):
421
421
 
422
422
  :param position: position
423
423
  """
424
- if self.seeking:
425
- self.player.positionChanged.disconnect(self.on_position_set)
426
- if self.player.playbackState() == QMediaPlayer.PausedState:
424
+ if self.seeking and self.player:
425
+ try:
426
+ self.player.positionChanged.disconnect(self.on_position_set)
427
+ except Exception:
428
+ pass
429
+ if self._QMediaPlayer and self.player.playbackState() == self._QMediaPlayer.PausedState:
427
430
  QTimer.singleShot(100, self.player.play)
428
431
  self.seeking = False
429
432
 
@@ -436,7 +439,7 @@ class VideoPlayerWidget(QWidget):
436
439
 
437
440
  def update_play_pause_icon(self):
438
441
  """Update play/pause icon"""
439
- if self.player.playbackState() == QMediaPlayer.PlayingState:
442
+ if self.player and self._QMediaPlayer and self.player.playbackState() == self._QMediaPlayer.PlayingState:
440
443
  self.btn_play_pause.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
441
444
  self.update_timer.start()
442
445
  else:
@@ -445,21 +448,24 @@ class VideoPlayerWidget(QWidget):
445
448
 
446
449
  def update_audio(self):
447
450
  """Re-assign audio"""
448
- self.player.setAudioOutput(None) # audio respawn fix
449
- self.player.setAudioOutput(self.audio)
451
+ if self.player and self.audio:
452
+ self.player.setAudioOutput(None)
453
+ self.player.setAudioOutput(self.audio)
450
454
 
451
455
  def update_mute_icon(self):
452
456
  """Update mute icon"""
453
- self.btn_mute.setChecked(self.audio.isMuted())
457
+ if self.audio:
458
+ self.btn_mute.setChecked(self.audio.isMuted())
454
459
 
455
460
  def update_volume_slider(self):
456
461
  """Update volume slider"""
457
- self.volume_slider.setValue(self.audio.volume() * 100)
462
+ if self.audio:
463
+ self.volume_slider.setValue(int(self.audio.volume() * 100))
458
464
 
459
465
  def update_ui(self):
460
466
  """Update UI"""
461
- # sync slider with the video time
462
- self.position_changed(self.player.position())
467
+ if self.player:
468
+ self.position_changed(self.player.position())
463
469
 
464
470
  def format_time(self, ms):
465
471
  """
@@ -467,7 +473,6 @@ class VideoPlayerWidget(QWidget):
467
473
 
468
474
  :param ms: milliseconds
469
475
  """
470
- # convert ms to H:M:S format
471
476
  seconds = (ms // 1000) % 60
472
477
  minutes = (ms // 60000) % 60
473
478
  hours = (ms // 3600000)
@@ -90,6 +90,13 @@ class CalendarSelect(QCalendarWidget):
90
90
  :param rect: Rectangle
91
91
  :param date: Date
92
92
  """
93
+ theme = self.window.core.config.get("theme")
94
+ if theme.startswith('dark'):
95
+ counter_bg = QColor(40, 40, 40)
96
+ counter_font = QColor(255, 255, 255)
97
+ else:
98
+ counter_bg = QColor(240, 240, 240)
99
+ counter_font = QColor(0, 0, 0)
93
100
 
94
101
  super().paintCell(painter, rect, date)
95
102
 
@@ -112,9 +119,10 @@ class CalendarSelect(QCalendarWidget):
112
119
  20,
113
120
  )
114
121
  painter.save()
115
- painter.setBrush(QBrush(QColor(40, 40, 40)))
122
+ painter.setBrush(QBrush(counter_bg))
123
+ painter.setPen(Qt.NoPen)
116
124
  painter.drawRect(task_rect)
117
- painter.setPen(QColor(255, 255, 255))
125
+ painter.setPen(counter_font)
118
126
  painter.setFont(QFont('Lato', self.font_size))
119
127
  painter.drawText(
120
128
  task_rect,
@@ -45,6 +45,7 @@ class FileExplorer(QWidget):
45
45
  self.treeView = QTreeView()
46
46
  self.treeView.setModel(self.model)
47
47
  self.treeView.setRootIndex(self.model.index(self.directory))
48
+ self.setProperty('class', 'file-explorer')
48
49
 
49
50
  header = QHBoxLayout()
50
51
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.5.98.post1
3
+ Version: 2.6.0.post1
4
4
  Summary: Desktop AI Assistant powered by: OpenAI GPT-5, o1, o3, GPT-4, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, internet access, file handling, command execution and more.
5
5
  License: MIT
6
6
  Keywords: py_gpt,py-gpt,pygpt,desktop,app,o1,o3,gpt-5,gpt,gpt4,gpt-4o,gpt-4v,gpt3.5,gpt-4,gpt-4-vision,gpt-3.5,llama3,mistral,gemini,grok,deepseek,bielik,claude,tts,whisper,vision,chatgpt,dall-e,chat,chatbot,assistant,text completion,image generation,ai,api,openai,api key,langchain,llama-index,ollama,presets,ui,qt,pyside
@@ -98,13 +98,15 @@ Requires-Dist: youtube-transcript-api (>=0.6.3,<0.7.0)
98
98
  Project-URL: Documentation, https://pygpt.readthedocs.io/
99
99
  Project-URL: Homepage, https://pygpt.net
100
100
  Project-URL: Repository, https://github.com/szczyglis-dev/py-gpt
101
+ Project-URL: changelog, https://github.com/szczyglis-dev/py-gpt/blob/master/CHANGELOG.md
102
+ Project-URL: issues, https://github.com/szczyglis-dev/py-gpt/issues
101
103
  Description-Content-Type: text/markdown
102
104
 
103
105
  # PyGPT - Desktop AI Assistant
104
106
 
105
107
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
106
108
 
107
- Release: **2.5.98** | build: **2025-08-12** | Python: **>=3.10, <3.14**
109
+ Release: **2.6.0** | build: **2025-08-13** | Python: **>=3.10, <3.14**
108
110
 
109
111
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
110
112
  >
@@ -3384,7 +3386,7 @@ Enable/disable remote tools, like Web Search or Image generation to use in OpenA
3384
3386
 
3385
3387
  - `Verbose` - enables verbose mode.
3386
3388
 
3387
- - `Split response messages` - Split response messages to separated context items in OpenAI Agents mode. Stream mode only.
3389
+ - `Split response messages` - Split response messages to separated context items in OpenAI Agents mode.
3388
3390
 
3389
3391
  **Autonomous (Legacy agents)**
3390
3392
 
@@ -4369,6 +4371,15 @@ may consume additional tokens that are not displayed in the main window.
4369
4371
 
4370
4372
  ## Recent changes:
4371
4373
 
4374
+ **2.6.0 (2025-08-13)**
4375
+
4376
+ - Added split responses to the OpenAI Agents in non-streaming mode.
4377
+ - Disabled auto-scroll when manually scrolled to the top.
4378
+ - Increased scrollbar width in the light theme.
4379
+ - Optimized the clearing of the streaming buffer.
4380
+ - Optimized imports.
4381
+ - Made CSS improvements.
4382
+
4372
4383
  **2.5.98 (2025-08-12)**
4373
4384
 
4374
4385
  - Added support for GPT-5 in LlamaIndex/Chat with Files mode.
@@ -4400,150 +4411,6 @@ may consume additional tokens that are not displayed in the main window.
4400
4411
  - Fixed: Storing the last used context ID when empty.
4401
4412
  - Fixed: Reloading items when an agent run is stopped.
4402
4413
 
4403
- **2.5.93 (2025-08-08)**
4404
-
4405
- - Added a new tool: Translate - in menu Tools - feature #123.
4406
-
4407
- **2.5.92 (2025-08-08)**
4408
-
4409
- - Added max files to store config option in Audio -> Cache.
4410
-
4411
- **2.5.91 (2025-08-08)**
4412
-
4413
- - Added GPT-5.
4414
- - Added audio cache - #118.
4415
-
4416
- **2.5.90 (2025-08-07)**
4417
-
4418
- - Fix: Initialize context summary if a conversation starts with a tool call.
4419
- - Fix: Store splitter positions even if the object is deleted from memory.
4420
- - Update: CSS improvements.
4421
-
4422
- **2.5.89 (2025-08-07)**
4423
-
4424
- - Added audio output device selection in Config -> Audio - issue #117
4425
- - Added audio input and output backend selections in Config -> Audio.
4426
-
4427
- **2.5.88 (2025-08-06)**
4428
-
4429
- - Optimized the process of unloading tabs from memory.
4430
- - Reduced initial RAM usage on launch.
4431
- - Added a handler for the SIGTERM signal.
4432
-
4433
- **2.5.87 (2025-08-05)**
4434
-
4435
- - Optimized memory cleanup.
4436
-
4437
- **2.5.86 (2025-08-04)**
4438
-
4439
- - Optimized CPU and memory usage.
4440
- - OpenAI vector stores tool added to Tools menu.
4441
- - Fixed schema parsing for tools in Agent (OpenAI) mode.
4442
- - Fixed multi-threading when uploading to remote vector store.
4443
- - Fixed Urls open in Snap.
4444
-
4445
- **2.5.85 (2025-08-02)**
4446
-
4447
- - Added importer for models from providers in Config -> Models -> Import... - #127
4448
- - Fix: options save in models editor.
4449
-
4450
- **2.5.84 (2025-08-02)**
4451
-
4452
- - Added a new OpenAI agent mode: Evolve. You can find its description in the documentation under the section Modes -> Agent (OpenAI).
4453
-
4454
- **2.5.83 (2025-08-01)**
4455
-
4456
- - Improved streaming in Agent (OpenAI) mode.
4457
- - Improved loading of default options in presets.
4458
- - Added context summary event if the kernel stopped.
4459
- - Implemented dynamic width for opened combo boxes.
4460
-
4461
- **2.5.82 (2025-08-01)**
4462
-
4463
- - Added a new OpenAI agents: researcher, planner, feedback.
4464
-
4465
- **2.5.81 (2025-07-31)**
4466
-
4467
- - Disabled remote tools by default in non-OpenAI providers in Agent (OpenAI) mode.
4468
- - Removed unsupported models from Agent (OpenAI) mode.
4469
-
4470
- **2.5.80 (2025-07-30)**
4471
-
4472
- - Improved the stop command in Agent mode: added saving of current output on break.
4473
- - Fixed the experts providers list in Agent (LlamaIndex): now only Llama providers are displayed.
4474
- - Added max_turns configuration to Agent (OpenAI), shared with Agents -> Max steps configuration option value.
4475
-
4476
- **2.5.79 (2025-07-30)**
4477
-
4478
- - Added prevent text break on input send when the cursor is inside the text.
4479
- - Fix: updating expert IDs on preset save.
4480
- - Added notification if no agent is selected.
4481
- - Disabled FFmpeg warnings.
4482
-
4483
- **2.5.78 (2025-07-30)**
4484
-
4485
- - Added support for Python 3.13.
4486
- - Fixed history prepare in Agent (LlamaIndex) mode.
4487
-
4488
- **2.5.77 (2025-07-30)**
4489
-
4490
- - Fix: history prepare in Agent (OpenAI) mode.
4491
- - Agent response evaluation splitted into two modes: by the percentage of task completion and by accuracy.
4492
-
4493
- **2.5.76 (2025-07-30)**
4494
-
4495
- - Added a new mode: Agent (OpenAI) and integrated `openai-agents` into the app (beta).
4496
-
4497
- **2.5.75 (2025-07-28)**
4498
-
4499
- - Fix: context append in LlamaIndex agents.
4500
-
4501
- **2.5.74 (2025-07-28)**
4502
-
4503
- - Fix: mouse buttons handling in Computer use mode.
4504
- - Added double click handler.
4505
-
4506
- **2.5.73 (2025-07-28)**
4507
-
4508
- - Stream rendering optimization, reduced CPU usage.
4509
- - Fix: < and > rendering in math formulas.
4510
- - Fix: do not automatically add tools to the agent when the inline plugin is enabled but the Tools option is not enabled.
4511
- - Fix: corrected saving of the last context element when the stream is interrupted.
4512
- - Fix: unlock response regeneration after stopped event.
4513
-
4514
- **2.5.72 (2025-07-27)**
4515
-
4516
- - Improved stop command.
4517
-
4518
- **2.5.71 (2025-07-27)**
4519
-
4520
- - Added a new working mode: `Computer use` for autonomous navigation in the user's environment (beta; utilizes the `Computer use` remote tool and the model `computer-use-preview`).
4521
- - Added a new remote tool: `Remote MCP` (with configuration in Settings -> Remote Tools).
4522
- - Added a new remote tool: `File Search` (with configuration in Settings -> Remote Tools).
4523
- - Added a new option `Always continue...` to Agent (autonomous) plugin settings.
4524
-
4525
- **2.5.70 (2025-07-26)**
4526
-
4527
- - Added separate config for Responses API for expert instances.
4528
- - Added a new model: mistral-small3.1.
4529
-
4530
- **2.5.69 (2025-07-25)**
4531
-
4532
- - The Responses API and remote tools are now allowed in Agent (autonomous) and Expert modes. Default: disabled.
4533
- - Added separate options in the configuration for enabling the Responses API in: Config -> Agents and Experts.
4534
- - Improved expert and agents system prompt.
4535
-
4536
- **2.5.68 (2025-07-25)**
4537
-
4538
- - Added a separate configuration to enable or disable native function calls in both agent and expert modes.
4539
-
4540
- **2.5.67 (2025-07-25)**
4541
-
4542
- - Added native tool call functionality in Experts.
4543
- - Enhanced the use of multiple tool calls in Experts.
4544
- - Fixed the display of Expert names when responding.
4545
- - Improved context history handling in Experts.
4546
-
4547
4414
  # Credits and links
4548
4415
 
4549
4416
  **Official website:** <https://pygpt.net>