pygpt-net 2.4.50__py3-none-any.whl → 2.4.52__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 (33) hide show
  1. CHANGELOG.md +11 -0
  2. README.md +12 -62
  3. pygpt_net/CHANGELOG.txt +11 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/controller/audio/__init__.py +86 -1
  6. pygpt_net/controller/lang/custom.py +2 -1
  7. pygpt_net/controller/plugins/__init__.py +5 -30
  8. pygpt_net/controller/ui/tabs.py +14 -3
  9. pygpt_net/core/audio/capture.py +19 -1
  10. pygpt_net/data/config/config.json +5 -3
  11. pygpt_net/data/config/models.json +3 -3
  12. pygpt_net/data/config/modes.json +3 -3
  13. pygpt_net/data/config/settings.json +13 -0
  14. pygpt_net/data/locale/locale.de.ini +3 -0
  15. pygpt_net/data/locale/locale.en.ini +3 -0
  16. pygpt_net/data/locale/locale.es.ini +3 -0
  17. pygpt_net/data/locale/locale.fr.ini +3 -0
  18. pygpt_net/data/locale/locale.it.ini +3 -0
  19. pygpt_net/data/locale/locale.pl.ini +3 -0
  20. pygpt_net/data/locale/locale.uk.ini +3 -0
  21. pygpt_net/data/locale/locale.zh.ini +3 -0
  22. pygpt_net/plugin/audio_input/simple.py +31 -8
  23. pygpt_net/plugin/cmd_files/worker.py +37 -1
  24. pygpt_net/provider/core/config/patch.py +9 -1
  25. pygpt_net/ui/__init__.py +4 -2
  26. pygpt_net/ui/layout/chat/input.py +0 -12
  27. pygpt_net/ui/menu/__init__.py +4 -3
  28. pygpt_net/ui/widget/audio/input_button.py +31 -23
  29. {pygpt_net-2.4.50.dist-info → pygpt_net-2.4.52.dist-info}/LICENSE +1 -1
  30. {pygpt_net-2.4.50.dist-info → pygpt_net-2.4.52.dist-info}/METADATA +13 -63
  31. {pygpt_net-2.4.50.dist-info → pygpt_net-2.4.52.dist-info}/RECORD +33 -33
  32. {pygpt_net-2.4.50.dist-info → pygpt_net-2.4.52.dist-info}/WHEEL +0 -0
  33. {pygpt_net-2.4.50.dist-info → pygpt_net-2.4.52.dist-info}/entry_points.txt +0 -0
@@ -136,6 +136,7 @@ audio.control.btn = 语音控制
136
136
  audio.magic_word.detected = 檢測到魔法詞!
137
137
  audio.magic_word.invalid= 不是魔法詞 :(
138
138
  audio.magic_word.please= 請說出魔法詞...
139
+ audio.speak.btn.continuous = 连续录音
139
140
  audio.speak.btn.stop = 停止
140
141
  audio.speak.btn.stop.tooltip = 點擊停止麥克風聆聽
141
142
  audio.speak.btn.tooltip = 點擊開始麥克風聆聽
@@ -875,6 +876,8 @@ settings.audio.input.device = 音频输入设备
875
876
  settings.audio.input.device.desc = 选择用于麦克风输入的音频设备。
876
877
  settings.audio.input.rate = 采样率
877
878
  settings.audio.input.rate.desc = 采样率,默认: 44100
879
+ settings.audio.input.stop_interval = 连续录音自动转录间隔
880
+ settings.audio.input.stop_interval.desc = 自动转录音频片段的间隔(以秒为单位),默认:10
878
881
  settings.check_updates = 啟動時檢查更新
879
882
  settings.check_updates.bg = 在後台檢查更新
880
883
  settings.cmd.field.desc = 启用 `{cmd}` 工具。
@@ -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.01.16 17:00:00 #
9
+ # Updated Date: 2025.01.17 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -14,6 +14,7 @@ import os
14
14
  from PySide6.QtCore import QTimer
15
15
 
16
16
  from pygpt_net.core.events import AppEvent
17
+ from pygpt_net.core.tabs.tab import Tab
17
18
  from pygpt_net.utils import trans
18
19
 
19
20
 
@@ -54,8 +55,20 @@ class Simple:
54
55
  """Stop timeout"""
55
56
  self.stop_recording(timeout=True)
56
57
 
57
- def start_recording(self):
58
- """Start recording"""
58
+ def start_recording(self, force: bool = False):
59
+ """
60
+ Start recording
61
+
62
+ :param force: True to force recording
63
+ """
64
+ # enable continuous mode if notepad tab is active
65
+ self.plugin.window.core.audio.capture.stop_callback = self.on_stop
66
+ continuous_enabled = self.plugin.window.core.config.get('audio.input.continuous', False)
67
+ if continuous_enabled and self.plugin.window.controller.ui.tabs.get_current_type() == Tab.TAB_NOTEPAD:
68
+ self.plugin.window.core.audio.capture.loop = True # set loop
69
+ else:
70
+ self.plugin.window.core.audio.capture.loop = False
71
+
59
72
  try:
60
73
  # stop audio output if playing
61
74
  if self.plugin.window.controller.audio.is_playing():
@@ -72,11 +85,12 @@ class Simple:
72
85
  self.timer.timeout.connect(self.stop_timeout)
73
86
  self.timer.start(self.TIMEOUT_SECONDS * 1000)
74
87
 
75
- if not self.plugin.window.core.audio.capture.check_audio_input():
76
- raise Exception("Audio input not working.")
77
- # IMPORTANT!!!!
78
- # Stop here if audio input not working!
79
- # This prevents the app from freezing when audio input is not working!
88
+ if not force:
89
+ if not self.plugin.window.core.audio.capture.check_audio_input():
90
+ raise Exception("Audio input not working.")
91
+ # IMPORTANT!!!!
92
+ # Stop here if audio input not working!
93
+ # This prevents the app from freezing when audio input is not working!
80
94
 
81
95
  self.is_recording = True
82
96
  self.switch_btn_stop()
@@ -128,3 +142,12 @@ class Simple:
128
142
  self.plugin.handle_thread(True) # handle transcription in simple mode
129
143
  else:
130
144
  self.plugin.window.update_status("")
145
+
146
+
147
+ def on_stop(self):
148
+ """Handle auto-transcribe"""
149
+ path = os.path.join(self.plugin.window.core.config.path, self.plugin.input_file)
150
+ self.plugin.window.core.audio.capture.set_path(path)
151
+ self.plugin.window.core.audio.capture.stop()
152
+ self.plugin.window.core.audio.capture.start()
153
+ self.plugin.handle_thread(True)
@@ -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: 2024.11.18 21:00:00 #
9
+ # Updated Date: 2025.01.17 13:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import fnmatch
@@ -160,6 +160,8 @@ class Worker(BaseWorker):
160
160
  :return: response item
161
161
  """
162
162
  try:
163
+ if "path" not in item["params"] or "data" not in item["params"]:
164
+ return self.make_response(item, "Path or data not provided")
163
165
  path = self.prepare_path(item["params"]['path'])
164
166
  data = item["params"]['data']
165
167
  self.msg = "Saving file: {}".format(path)
@@ -180,6 +182,8 @@ class Worker(BaseWorker):
180
182
  :return: response item
181
183
  """
182
184
  try:
185
+ if "path" not in item["params"] or "data" not in item["params"]:
186
+ return self.make_response(item, "Path or data not provided")
183
187
  path = self.prepare_path(item["params"]['path'])
184
188
  data = item["params"]['data']
185
189
  self.msg = "Appending file: {}".format(path)
@@ -201,6 +205,8 @@ class Worker(BaseWorker):
201
205
  """
202
206
  context_result = ""
203
207
  try:
208
+ if "path" not in item["params"]:
209
+ return self.make_response(item, "Path not provided")
204
210
  self.msg = "Reading file: {}".format(item["params"]['path'])
205
211
  self.log(self.msg)
206
212
  path = item["params"]['path']
@@ -232,6 +238,8 @@ class Worker(BaseWorker):
232
238
  context = None
233
239
  query = None
234
240
  try:
241
+ if "path" not in item["params"]:
242
+ return self.make_response(item, "Path not provided")
235
243
  path = self.prepare_path(item["params"]['path'])
236
244
  self.msg = "Reading path: {}".format(path)
237
245
  self.log(self.msg)
@@ -283,6 +291,8 @@ class Worker(BaseWorker):
283
291
  :return: response item
284
292
  """
285
293
  try:
294
+ if "path" not in item["params"]:
295
+ return self.make_response(item, "Path not provided")
286
296
  path = self.prepare_path(item["params"]['path'])
287
297
  self.msg = "Deleting file: {}".format(path)
288
298
  self.log(self.msg)
@@ -372,6 +382,8 @@ class Worker(BaseWorker):
372
382
  :return: response item
373
383
  """
374
384
  try:
385
+ if "path" not in item["params"]:
386
+ return self.make_response(item, "Path not provided")
375
387
  path = self.prepare_path(item["params"]['path'])
376
388
  self.msg = "Creating directory: {}".format(path)
377
389
  self.log(self.msg)
@@ -394,6 +406,8 @@ class Worker(BaseWorker):
394
406
  :return: response item
395
407
  """
396
408
  try:
409
+ if "path" not in item["params"]:
410
+ return self.make_response(item, "Path not provided")
397
411
  path = self.prepare_path(item["params"]['path'])
398
412
  self.msg = "Deleting directory: {}".format(path)
399
413
  self.log(self.msg)
@@ -416,6 +430,8 @@ class Worker(BaseWorker):
416
430
  :return: response item
417
431
  """
418
432
  try:
433
+ if "src" not in item["params"] or "dst" not in item["params"]:
434
+ return self.make_response(item, "Source or destination not provided")
419
435
  dst = self.prepare_path(item["params"]['dst'])
420
436
  self.msg = "Downloading file: {} into {}".format(item["params"]['src'], dst)
421
437
  self.log(self.msg)
@@ -468,6 +484,8 @@ class Worker(BaseWorker):
468
484
  :return: response item
469
485
  """
470
486
  try:
487
+ if "src" not in item["params"] or "dst" not in item["params"]:
488
+ return self.make_response(item, "Source or destination not provided")
471
489
  src = self.prepare_path(item["params"]['src'])
472
490
  dst = self.prepare_path(item["params"]['dst'])
473
491
  self.msg = "Copying file: {} into {}".format(src, dst)
@@ -487,6 +505,8 @@ class Worker(BaseWorker):
487
505
  :return: response item
488
506
  """
489
507
  try:
508
+ if "src" not in item["params"] or "dst" not in item["params"]:
509
+ return self.make_response(item, "Source or destination not provided")
490
510
  src = self.prepare_path(item["params"]['src'])
491
511
  dst = self.prepare_path(item["params"]['dst'])
492
512
  self.msg = "Copying directory: {} into {}".format(src, dst)
@@ -506,6 +526,8 @@ class Worker(BaseWorker):
506
526
  :return: response item
507
527
  """
508
528
  try:
529
+ if "src" not in item["params"] or "dst" not in item["params"]:
530
+ return self.make_response(item, "Source or destination not provided")
509
531
  src = self.prepare_path(item["params"]['src'])
510
532
  dst = self.prepare_path(item["params"]['dst'])
511
533
  self.msg = "Moving: {} into {}".format(src, dst)
@@ -525,6 +547,8 @@ class Worker(BaseWorker):
525
547
  :return: response item
526
548
  """
527
549
  try:
550
+ if "path" not in item["params"]:
551
+ return self.make_response(item, "Path not provided")
528
552
  path = self.prepare_path(item["params"]['path'])
529
553
  self.msg = "Checking if directory exists: {}".format(path)
530
554
  self.log(self.msg)
@@ -546,6 +570,8 @@ class Worker(BaseWorker):
546
570
  :return: response item
547
571
  """
548
572
  try:
573
+ if "path" not in item["params"]:
574
+ return self.make_response(item, "Path not provided")
549
575
  path = self.prepare_path(item["params"]['path'])
550
576
  self.msg = "Checking if file exists: {}".format(path)
551
577
  self.log(self.msg)
@@ -567,6 +593,8 @@ class Worker(BaseWorker):
567
593
  :return: response item
568
594
  """
569
595
  try:
596
+ if "path" not in item["params"]:
597
+ return self.make_response(item, "Path not provided")
570
598
  path = self.prepare_path(item["params"]['path'])
571
599
  self.msg = "Checking if path exists: {}".format(path)
572
600
  self.log(self.msg)
@@ -588,6 +616,8 @@ class Worker(BaseWorker):
588
616
  :return: response item
589
617
  """
590
618
  try:
619
+ if "path" not in item["params"]:
620
+ return self.make_response(item, "Path not provided")
591
621
  path = self.prepare_path(item["params"]['path'])
592
622
  self.msg = "Checking file size: {}".format(path)
593
623
  self.log(self.msg)
@@ -613,6 +643,8 @@ class Worker(BaseWorker):
613
643
  :return: response item
614
644
  """
615
645
  try:
646
+ if "path" not in item["params"]:
647
+ return self.make_response(item, "Path not provided")
616
648
  path = self.prepare_path(item["params"]['path'])
617
649
  self.msg = "Checking file info: {}".format(path)
618
650
  self.log(self.msg)
@@ -666,6 +698,8 @@ class Worker(BaseWorker):
666
698
  :return: response item
667
699
  """
668
700
  try:
701
+ if "path" not in item["params"]:
702
+ return self.make_response(item, "Path not provided")
669
703
  path = self.prepare_path(item["params"]['path'])
670
704
  self.msg = "Adding attachment: {}".format(path)
671
705
  self.log(self.msg)
@@ -728,6 +762,8 @@ class Worker(BaseWorker):
728
762
  :return: response item
729
763
  """
730
764
  try:
765
+ if "pattern" not in item["params"]:
766
+ return self.make_response(item, "Search pattern not provided")
731
767
  recursive = True
732
768
  path = self.plugin.window.core.config.get_user_dir('data')
733
769
  pattern = item["params"]['pattern']
@@ -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: 2024.12.16 20:00:00 #
9
+ # Updated Date: 2025.01.17 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -1803,6 +1803,14 @@ class Patch:
1803
1803
  data["api_key_hugging_face"] = ""
1804
1804
  updated = True
1805
1805
 
1806
+ # < 2.4.51
1807
+ if old < parse_version("2.4.51"):
1808
+ print("Migrating config from < 2.4.51...")
1809
+ if 'audio.input.stop_interval' not in data:
1810
+ data["audio.input.stop_interval"] = 10
1811
+ if 'audio.input.continuous' not in data:
1812
+ data["audio.input.continuous"] = False
1813
+
1806
1814
  # update file
1807
1815
  migrated = False
1808
1816
  if updated:
pygpt_net/ui/__init__.py CHANGED
@@ -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: 2024.11.21 17:00:00 #
9
+ # Updated Date: 2025.01.17 13:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -109,9 +109,10 @@ class UI:
109
109
  def set_initial_size(self):
110
110
  """Set default sizes"""
111
111
  def set_initial_splitter_height():
112
+ """Set initial splitter height"""
112
113
  total_height = self.window.ui.splitters['main.output'].size().height()
113
114
  if total_height > 0:
114
- size_output = int(total_height * 0.8)
115
+ size_output = int(total_height * 0.9)
115
116
  size_input = total_height - size_output
116
117
  self.window.ui.splitters['main.output'].setSizes([size_output, size_input])
117
118
  else:
@@ -119,6 +120,7 @@ class UI:
119
120
  QTimer.singleShot(0, set_initial_splitter_height)
120
121
 
121
122
  def set_initial_splitter_width():
123
+ """Set initial splitter width"""
122
124
  total_width = self.window.ui.splitters['main'].size().width()
123
125
  if total_width > 0:
124
126
  size_output = int(total_width * 0.7)
@@ -160,24 +160,12 @@ class Input:
160
160
 
161
161
  grid = QGridLayout()
162
162
 
163
- #left_layout = QHBoxLayout()
164
- #left_layout.addWidget(self.window.ui.nodes['input.label'])
165
- #left_layout.addWidget(self.window.ui.nodes['inline.vision'])
166
- #left_layout.addStretch(1)
167
-
168
163
  center_layout = QHBoxLayout()
169
164
  center_layout.addStretch()
170
165
  center_layout.addWidget(self.window.ui.plugin_addon['audio.input'])
171
166
  center_layout.addWidget(self.window.ui.plugin_addon['audio.input.btn'])
172
167
  center_layout.addStretch()
173
-
174
- #right_layout = QHBoxLayout()
175
- #right_layout.addStretch(1)
176
- #right_layout.addWidget(self.window.ui.nodes['input.counter'])
177
-
178
- #grid.addLayout(left_layout, 0, 0)
179
168
  grid.addLayout(center_layout, 0, 1, alignment=Qt.AlignCenter)
180
- #grid.addLayout(right_layout, 0, 2, alignment=Qt.AlignRight)
181
169
 
182
170
  grid.setContentsMargins(0, 0, 0, 0)
183
171
  return grid
@@ -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.01.16 01:00:00 #
9
+ # Updated Date: 2025.01.17 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from .about import About
@@ -50,15 +50,16 @@ class Menu:
50
50
  self.audio.setup()
51
51
  self.video.setup()
52
52
  self.config.setup()
53
- self.about.setup()
54
53
 
55
54
  def post_setup(self):
56
55
  """Post setup menus"""
57
56
  # tools menu
58
57
  self.tools.setup()
59
- self.donate.setup()
58
+ self.about.setup()
60
59
 
61
60
  # debug menu
62
61
  show = self.window.core.config.get('debug')
63
62
  self.debug.setup()
64
63
  self.window.ui.menu['menu.debug'].menuAction().setVisible(show)
64
+
65
+ self.donate.setup()
@@ -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.01.16 17:00:00 #
9
+ # Updated Date: 2025.01.17 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
13
- from PySide6.QtGui import QPainter
13
+ from PySide6.QtGui import QPainter, QIcon
14
14
  from PySide6.QtWidgets import QLabel, QHBoxLayout, QWidget, QPushButton, QVBoxLayout
15
15
 
16
16
  from pygpt_net.core.events import Event, AppEvent
17
+ from pygpt_net.ui.widget.option.toggle_label import ToggleLabel
17
18
  from pygpt_net.utils import trans
19
+ import pygpt_net.icons_rc
18
20
 
19
21
  class VoiceControlButton(QWidget):
20
22
  def __init__(self, window=None):
@@ -26,7 +28,7 @@ class VoiceControlButton(QWidget):
26
28
  super(VoiceControlButton, self).__init__(window)
27
29
  self.window = window
28
30
 
29
- self.btn_toggle = QPushButton(trans('audio.control.btn'))
31
+ self.btn_toggle = QPushButton(QIcon(":/icons/mic.svg"), trans('audio.control.btn'))
30
32
  self.btn_toggle.clicked.connect(self.toggle_recording)
31
33
  self.btn_toggle.setToolTip(trans('audio.speak.btn.tooltip'))
32
34
  self.btn_toggle.setCursor(Qt.PointingHandCursor)
@@ -81,7 +83,7 @@ class AudioInputButton(QWidget):
81
83
  super(AudioInputButton, self).__init__(window)
82
84
  self.window = window
83
85
 
84
- self.btn_toggle = QPushButton(trans('audio.speak.btn'))
86
+ self.btn_toggle = QPushButton(QIcon(":/icons/mic.svg"), trans('audio.speak.btn'))
85
87
  self.btn_toggle.clicked.connect(self.toggle_recording)
86
88
  self.btn_toggle.setToolTip(trans('audio.speak.btn.tooltip'))
87
89
  self.btn_toggle.setCursor(Qt.PointingHandCursor)
@@ -90,20 +92,34 @@ class AudioInputButton(QWidget):
90
92
  self.bar = LevelBar(self)
91
93
  self.bar.setLevel(0)
92
94
 
93
- # status
94
- #self.status = QLabel("xxx")
95
- #self.status.setStyleSheet("color: #999; font-size: 10px; font-weight: 400; margin: 0; padding: 0; border: 0;")
96
- #self.status.setMaximumHeight(15)
95
+ btn_layout = QVBoxLayout()
96
+ btn_layout.addWidget(self.btn_toggle)
97
+ btn_layout.addWidget(self.bar)
98
+ btn_layout.setContentsMargins(0, 0, 0, 0)
99
+ btn_widget = QWidget()
100
+ btn_widget.setLayout(btn_layout)
97
101
 
98
- self.layout = QVBoxLayout(self)
99
- self.layout.addWidget(self.btn_toggle)
100
- #self.layout.addWidget(self.status)
101
- self.layout.addWidget(self.bar)
102
+ self.continuous = ToggleLabel(trans('audio.speak.btn.continuous'), label_position="right")
103
+ self.continuous.box.stateChanged.connect(
104
+ lambda: self.window.controller.audio.toggle_continuous(
105
+ self.continuous.box.isChecked())
106
+ )
107
+
108
+ self.notepad_layout = QHBoxLayout()
109
+ self.notepad_layout.addWidget(self.continuous)
110
+ self.notepad_layout.setContentsMargins(0, 0, 0, 0)
111
+
112
+ self.notepad_footer = QWidget()
113
+ self.notepad_footer.setLayout(self.notepad_layout)
114
+
115
+ self.layout = QHBoxLayout()
116
+ self.layout.addWidget(btn_widget)
117
+ self.layout.addWidget(self.notepad_footer)
118
+ self.layout.setContentsMargins(0, 0, 0, 0)
102
119
 
103
- # self.layout.addWidget(self.stop)
104
- self.layout.setAlignment(Qt.AlignCenter)
105
120
  self.setLayout(self.layout)
106
- self.setMaximumHeight(80)
121
+ btn_widget.setMaximumHeight(80)
122
+ self.setMaximumHeight(120)
107
123
 
108
124
  def add_widget(self, widget):
109
125
  """
@@ -113,14 +129,6 @@ class AudioInputButton(QWidget):
113
129
  """
114
130
  self.layout.addWidget(widget)
115
131
 
116
- def set_status(self, text):
117
- """
118
- Set status text
119
-
120
- :param text: text
121
- """
122
- self.status.setText(text)
123
-
124
132
  def toggle_recording(self):
125
133
  """Toggle recording"""
126
134
  event = Event(Event.AUDIO_INPUT_RECORD_TOGGLE)
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Marcin Szczygliński
3
+ Copyright (c) 2025 Marcin Szczygliński
4
4
 
5
5
  GitHub: https://github.com/szczyglis-dev/py-gpt
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pygpt-net
3
- Version: 2.4.50
3
+ Version: 2.4.52
4
4
  Summary: Desktop AI Assistant powered by models: OpenAI o1, GPT-4o, GPT-4, GPT-4 Vision, GPT-3.5, DALL-E 3, Llama 3, Mistral, Gemini, Claude, Bielik, and other models supported by Langchain, Llama Index, and Ollama. Features include chatbot, text completion, image generation, vision analysis, speech-to-text, internet access, file handling, command execution and more.
5
5
  Home-page: https://pygpt.net
6
6
  License: MIT
@@ -93,7 +93,7 @@ Description-Content-Type: text/markdown
93
93
 
94
94
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
95
95
 
96
- Release: **2.4.50** | build: **2025.01.16** | Python: **>=3.10, <3.13**
96
+ Release: **2.4.52** | build: **2025.01.17** | Python: **>=3.10, <3.13**
97
97
 
98
98
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
99
99
  >
@@ -4044,6 +4044,17 @@ may consume additional tokens that are not displayed in the main window.
4044
4044
 
4045
4045
  ## Recent changes:
4046
4046
 
4047
+ **2.4.52 (2025-01-17)**
4048
+
4049
+ - Improved audio input button visibility toggle.
4050
+ - Fix: check for required arguments - issue #88.
4051
+ - UI Fixes.
4052
+
4053
+ **2.4.51 (2025-01-17)**
4054
+
4055
+ - Added a "Continuous recording" mode under Audio Input in the Notepad tab, allowing for recording long voice notes and real-time auto-transcription. (beta)
4056
+ - A new option has been added in Settings -> Audio -> Continuous recording auto-transcribe interval.
4057
+
4047
4058
  **2.4.50 (2025-01-16)**
4048
4059
 
4049
4060
  - Refactored audio input core.
@@ -4074,67 +4085,6 @@ may consume additional tokens that are not displayed in the main window.
4074
4085
  - Introduced a new mode in "Chat with Files": "Retrieve Only", which allows for retrieving raw documents from the index.
4075
4086
  - Fixed a bug related to tool calls in the Gemini provider when using Chat with Files mode.
4076
4087
 
4077
- **2.4.45 (2024-12-16)**
4078
-
4079
- - Enhanced web data loaders UI.
4080
-
4081
- **2.4.44 (2024-12-16)**
4082
-
4083
- - Enhanced web data loaders.
4084
- - Web loaders have been added to attachments, allowing external web content to be attached to context via the "+Web" button in the Attachments tab.
4085
- - Improved handling of attachments in groups and added an attachment icon when a group contains attachments.
4086
-
4087
- **2.4.43 (2024-12-15)**
4088
-
4089
- - Fix: Bug on attachment upload.
4090
- - Added: Attachments uploaded in groups are now available for all contexts in the group (beta).
4091
-
4092
- **2.4.42 (2024-12-15)**
4093
-
4094
- - Added Mailer plugin, which allows sending and retrieving emails from the server, and reading them. It currently supports only SMTP.
4095
- - Added 'web_request' command to the Web Search plugin, enabling GET/POST/PUT and other connections to any address and API endpoint. It also supports sending POST data, files, headers, cookies, and more.
4096
- - Improved audio output.
4097
- - Enhanced visibility of the Video menu.
4098
- - Other fixes.
4099
-
4100
- **2.4.41 (2024-12-14)**
4101
-
4102
- - Improved switching between columns on a split screen.
4103
- - Added visual identification of the active column.
4104
-
4105
- **2.4.40 (2024-12-13)**
4106
-
4107
- - Enhanced Split Screen mode, now promoted from beta to stable.
4108
- - Python Code Interpreter tool added to the Tabs.
4109
- - HTML/JS Canvas tool added to the Tabs.
4110
- - Added attachment icon to the context list if context has attachments.
4111
- - Improved audio playback.
4112
- - Improved web search.
4113
- - Added a thumbnail image to web search results.
4114
- - Added a new commands to web search: "extract_images" and "extract_links".
4115
- - Added the option "Use raw content (without summarization)" to the web search plugin, which provides a more detailed result to the main model.
4116
- - Extended the default maximum result characters to 50,000 in the web search plugin.
4117
-
4118
- **2.4.39 (2024-12-09)**
4119
-
4120
- - Added "Split Screen" mode (accessible via the switch in the bottom-right corner of the screen), which allows you to work in two windows simultaneously. It is currently experimental (beta). Future updates will include Code Interpreter and Canvas running in tabs.
4121
-
4122
- - Fixed: Language switch.
4123
-
4124
- **2.4.38 (2024-12-08)**
4125
-
4126
- - Added the ability to select a style for chat display between: Blocks, ChatGPT-like, and ChatGPT-like Wide. New option in the menu: Config -> Theme -> Style...
4127
- - Added configuration options for audio input in Settings -> Audio -> Audio Input Device, Channels, and Sampling rate.
4128
-
4129
- **2.4.37 (2024-11-30)**
4130
-
4131
- - The `Query only` mode in `Uploaded` tab has been renamed to `RAG`.
4132
- - New options have been added under `Settings -> Files and Attachments`:
4133
- - `Use history in RAG query`: When enabled, the content of the entire conversation will be used when preparing a query if the mode is set to RAG or Summary.
4134
- - `RAG limit`: This option is applicable only if 'Use history in RAG query' is enabled. It specifies the limit on how many recent entries in the conversation will be used when generating a query for RAG. A value of 0 indicates no limit.
4135
- - Cache: dynamic parts of the system prompt (from plugins) have been moved to the very end of the prompt stack to enable the use of prompt cache mechanisms in OpenAI.
4136
-
4137
-
4138
4088
  # Credits and links
4139
4089
 
4140
4090
  **Official website:** <https://pygpt.net>