pygpt-net 2.4.38__py3-none-any.whl → 2.4.39__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 (56) hide show
  1. CHANGELOG.md +6 -0
  2. README.md +7 -1
  3. pygpt_net/CHANGELOG.txt +6 -0
  4. pygpt_net/__init__.py +2 -2
  5. pygpt_net/controller/__init__.py +3 -1
  6. pygpt_net/controller/calendar/__init__.py +3 -1
  7. pygpt_net/controller/chat/render.py +8 -5
  8. pygpt_net/controller/ctx/__init__.py +32 -24
  9. pygpt_net/controller/ctx/common.py +3 -2
  10. pygpt_net/controller/dialogs/confirm.py +2 -2
  11. pygpt_net/controller/lang/custom.py +2 -7
  12. pygpt_net/controller/lang/mapping.py +2 -2
  13. pygpt_net/controller/layout.py +2 -2
  14. pygpt_net/controller/notepad.py +8 -5
  15. pygpt_net/controller/ui/tabs.py +201 -58
  16. pygpt_net/core/ctx/__init__.py +11 -1
  17. pygpt_net/core/ctx/container.py +16 -9
  18. pygpt_net/core/ctx/output.py +86 -67
  19. pygpt_net/core/debug/tabs.py +3 -2
  20. pygpt_net/core/filesystem/url.py +7 -3
  21. pygpt_net/core/render/base.py +14 -3
  22. pygpt_net/core/render/markdown/renderer.py +3 -1
  23. pygpt_net/core/render/plain/renderer.py +3 -3
  24. pygpt_net/core/render/web/body.py +9 -3
  25. pygpt_net/core/render/web/renderer.py +7 -5
  26. pygpt_net/core/tabs/__init__.py +180 -71
  27. pygpt_net/core/tabs/tab.py +13 -4
  28. pygpt_net/data/config/config.json +11 -5
  29. pygpt_net/data/config/models.json +3 -3
  30. pygpt_net/data/config/modes.json +3 -3
  31. pygpt_net/data/locale/locale.de.ini +3 -0
  32. pygpt_net/data/locale/locale.en.ini +3 -0
  33. pygpt_net/data/locale/locale.es.ini +3 -0
  34. pygpt_net/data/locale/locale.fr.ini +3 -0
  35. pygpt_net/data/locale/locale.it.ini +3 -0
  36. pygpt_net/data/locale/locale.pl.ini +4 -1
  37. pygpt_net/data/locale/locale.uk.ini +3 -0
  38. pygpt_net/data/locale/locale.zh.ini +3 -0
  39. pygpt_net/plugin/audio_input/simple.py +4 -2
  40. pygpt_net/provider/core/config/patch.py +8 -1
  41. pygpt_net/provider/core/ctx/base.py +4 -1
  42. pygpt_net/provider/core/ctx/db_sqlite/__init__.py +10 -1
  43. pygpt_net/provider/core/ctx/db_sqlite/storage.py +22 -1
  44. pygpt_net/ui/layout/chat/input.py +10 -18
  45. pygpt_net/ui/layout/chat/output.py +26 -44
  46. pygpt_net/ui/layout/toolbox/footer.py +18 -2
  47. pygpt_net/ui/widget/tabs/layout.py +195 -0
  48. pygpt_net/ui/widget/tabs/output.py +124 -35
  49. pygpt_net/ui/widget/textarea/html.py +11 -1
  50. pygpt_net/ui/widget/textarea/output.py +10 -1
  51. pygpt_net/ui/widget/textarea/web.py +49 -9
  52. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/METADATA +8 -2
  53. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/RECORD +56 -55
  54. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/LICENSE +0 -0
  55. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/WHEEL +0 -0
  56. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/entry_points.txt +0 -0
@@ -6,10 +6,10 @@
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.05 23:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from PySide6.QtWidgets import QTabWidget, QMenu
12
+ from PySide6.QtWidgets import QTabWidget, QMenu, QPushButton
13
13
  from PySide6.QtCore import Qt, Slot
14
14
  from PySide6.QtGui import QAction, QIcon
15
15
 
@@ -19,16 +19,59 @@ import pygpt_net.icons_rc
19
19
 
20
20
 
21
21
  class OutputTabs(QTabWidget):
22
- def __init__(self, window=None):
22
+ def __init__(self, window=None, column=None):
23
23
  super(OutputTabs, self).__init__(window)
24
24
  self.window = window
25
+ self.column = column
25
26
  self.setMinimumHeight(1)
26
27
  self.owner = None
27
28
  self.setMovable(True)
29
+ self.init()
30
+
31
+ def init(self):
32
+ """Initialize"""
33
+ # create the [+] button
34
+ add_button = QPushButton(QIcon(":/icons/add.svg"), "")
35
+ add_button.setFixedSize(30, 25)
36
+ add_button.setFlat(True)
37
+ add_button.clicked.connect(
38
+ lambda: self.window.controller.ui.tabs.new_tab(self.column.get_idx())
39
+ )
40
+ add_button.setObjectName('tab-add')
41
+ add_button.setProperty('tabAdd', True)
42
+ add_button.setToolTip(trans('action.tab.add.chat'))
43
+
44
+ # add the button to the top right corner of the tab bar
45
+ self.setCornerWidget(add_button, corner=Qt.TopRightCorner)
46
+
47
+ # connect signals
48
+ self.currentChanged.connect(
49
+ lambda: self.window.controller.ui.tabs.on_tab_changed(self.currentIndex(), self.column.get_idx())
50
+ )
51
+ self.tabBarClicked.connect(
52
+ lambda: self.window.controller.ui.tabs.on_tab_clicked(self.currentIndex(), self.column.get_idx())
53
+ )
54
+ self.tabBarDoubleClicked.connect(
55
+ lambda: self.window.controller.ui.tabs.on_tab_dbl_clicked(self.currentIndex(), self.column.get_idx())
56
+ )
57
+ self.tabCloseRequested.connect(
58
+ lambda: self.window.controller.ui.tabs.on_tab_closed(self.currentIndex(), self.column.get_idx())
59
+ )
60
+ self.tabBar().tabMoved.connect(
61
+ lambda: self.window.controller.ui.tabs.on_tab_moved(self.currentIndex(), self.column.get_idx())
62
+ )
63
+
64
+ def get_column(self):
65
+ """
66
+ Get column
67
+
68
+ :return: OutputColumn
69
+ """
70
+ return self.column
28
71
 
29
72
  def setOwner(self, owner: Tab):
30
73
  """
31
- Set parent
74
+ Set internal tab instance
32
75
 
33
76
  :param owner: parent tab instance
34
77
  """
@@ -42,56 +85,67 @@ class OutputTabs(QTabWidget):
42
85
  """
43
86
  if event.button() == Qt.RightButton:
44
87
  idx = self.tabBar().tabAt(event.pos())
45
- tab = self.window.core.tabs.get_tab_by_index(idx)
88
+ column_idx = self.column.get_idx()
89
+ tab = self.window.core.tabs.get_tab_by_index(idx, column_idx)
46
90
  if tab is not None:
47
91
  if tab.type == Tab.TAB_NOTEPAD:
48
- self.show_notepad_menu(idx, event.globalPos()) # notepad
92
+ self.show_notepad_menu(idx, column_idx, event.globalPos()) # notepad
49
93
  elif tab.type == Tab.TAB_CHAT:
50
- self.show_chat_menu(idx, event.globalPos()) # chat
94
+ self.show_chat_menu(idx, column_idx, event.globalPos()) # chat
51
95
  elif tab.type == Tab.TAB_FILES:
52
- self.show_files_menu(idx, event.globalPos()) # files
96
+ self.show_files_menu(idx, column_idx, event.globalPos()) # files
53
97
  else:
54
- self.show_default_menu(idx, event.globalPos()) # default
98
+ self.show_default_menu(idx, column_idx, event.globalPos()) # default
55
99
  super(OutputTabs, self).mousePressEvent(event)
56
100
 
57
- def get_common_actions(self, index):
101
+ def get_common_actions(self, index: int, column_idx: int):
58
102
  """
59
103
  Get common actions
60
104
 
61
105
  :param index: index
106
+ :param column_idx: column index
62
107
  :return: dict
63
108
  """
64
109
  actions = {}
65
110
  actions['add_chat'] = QAction(QIcon(":/icons/add.svg"), trans('action.tab.add.chat'), self)
66
111
  actions['add_chat'].triggered.connect(
67
- lambda: self.add_tab(index, Tab.TAB_CHAT)
112
+ lambda: self.add_tab(index, column_idx, Tab.TAB_CHAT)
68
113
  )
69
114
  actions['add_notepad'] = QAction(QIcon(":/icons/add.svg"), trans('action.tab.add.notepad'), self)
70
115
  actions['add_notepad'].triggered.connect(
71
- lambda: self.add_tab(index, Tab.TAB_NOTEPAD)
116
+ lambda: self.add_tab(index, column_idx, Tab.TAB_NOTEPAD)
72
117
  )
73
118
  actions['edit'] = QAction(QIcon(":/icons/edit.svg"), trans('action.rename'), self)
74
119
  actions['edit'].triggered.connect(
75
- lambda: self.rename_tab(index)
120
+ lambda: self.rename_tab(index, column_idx)
121
+ )
122
+ actions['move_right'] = QAction(QIcon(":/icons/forward"), trans('action.tab.move.right'), self)
123
+ actions['move_right'].triggered.connect(
124
+ lambda: self.window.controller.ui.tabs.move_tab(index, column_idx, 1)
125
+ )
126
+ actions['move_left'] = QAction(QIcon(":/icons/back"), trans('action.tab.move.left'), self)
127
+ actions['move_left'].triggered.connect(
128
+ lambda: self.window.controller.ui.tabs.move_tab(index, column_idx, 0)
76
129
  )
77
130
  return actions
78
131
 
79
- def show_notepad_menu(self, index, global_pos):
132
+ def show_notepad_menu(self, index: int, column_idx: int, global_pos):
80
133
  """
81
134
  Show notepad menu
82
135
 
83
136
  :param index: index
137
+ :param column_idx: column index
84
138
  :param global_pos: global position
85
139
  """
86
140
  context_menu = QMenu()
87
- actions = self.get_common_actions(index)
141
+ actions = self.get_common_actions(index, column_idx)
88
142
  actions['close'] = QAction(QIcon(":/icons/close.svg"), trans('action.tab.close'), self)
89
143
  actions['close'].triggered.connect(
90
- lambda: self.close_tab(index)
144
+ lambda: self.close_tab(index, column_idx)
91
145
  )
92
146
  actions['close_all'] = QAction(QIcon(":/icons/close.svg"), trans('action.tab.close_all.notepad'), self)
93
147
  actions['close_all'].triggered.connect(
94
- lambda: self.close_all(Tab.TAB_NOTEPAD)
148
+ lambda: self.close_all(Tab.TAB_NOTEPAD, column_idx)
95
149
  )
96
150
  context_menu.addAction(actions['add_chat'])
97
151
  context_menu.addAction(actions['add_notepad'])
@@ -100,24 +154,32 @@ class OutputTabs(QTabWidget):
100
154
 
101
155
  if self.window.core.tabs.count_by_type(Tab.TAB_NOTEPAD) > 1:
102
156
  context_menu.addAction(actions['close_all'])
157
+
158
+ # move
159
+ if column_idx != 0:
160
+ context_menu.addAction(actions['move_left'])
161
+ if column_idx != 1:
162
+ context_menu.addAction(actions['move_right'])
163
+
103
164
  context_menu.exec(global_pos)
104
165
 
105
- def show_chat_menu(self, index, global_pos):
166
+ def show_chat_menu(self, index: int, column_idx: int, global_pos):
106
167
  """
107
168
  Show chat menu
108
169
 
109
170
  :param index: index
171
+ :param column_idx: column index
110
172
  :param global_pos: global position
111
173
  """
112
174
  context_menu = QMenu()
113
- actions = self.get_common_actions(index)
175
+ actions = self.get_common_actions(index, column_idx)
114
176
  actions['close'] = QAction(QIcon(":/icons/close.svg"), trans('action.tab.close'), self)
115
177
  actions['close'].triggered.connect(
116
- lambda: self.close_tab(index)
178
+ lambda: self.close_tab(index, column_idx)
117
179
  )
118
180
  actions['close_all'] = QAction(QIcon(":/icons/close.svg"), trans('action.tab.close_all.chat'), self)
119
181
  actions['close_all'].triggered.connect(
120
- lambda: self.close_all(Tab.TAB_CHAT)
182
+ lambda: self.close_all(Tab.TAB_CHAT, column_idx)
121
183
  )
122
184
  context_menu.addAction(actions['add_chat'])
123
185
  context_menu.addAction(actions['add_notepad'])
@@ -126,21 +188,26 @@ class OutputTabs(QTabWidget):
126
188
  # at least one chat tab must be open
127
189
  if self.window.core.tabs.count_by_type(Tab.TAB_CHAT) > 1:
128
190
  context_menu.addAction(actions['close'])
129
-
130
- if self.window.core.tabs.count_by_type(Tab.TAB_CHAT) > 1:
131
191
  context_menu.addAction(actions['close_all'])
132
192
 
193
+ # move
194
+ if column_idx != 0:
195
+ context_menu.addAction(actions['move_left'])
196
+ if column_idx != 1:
197
+ context_menu.addAction(actions['move_right'])
198
+
133
199
  context_menu.exec(global_pos)
134
200
 
135
- def show_files_menu(self, index, global_pos):
201
+ def show_files_menu(self, index: int, column_idx: int, global_pos):
136
202
  """
137
203
  Show files menu
138
204
 
139
205
  :param index: index
206
+ :param column_idx: column index
140
207
  :param global_pos: global position
141
208
  """
142
209
  context_menu = QMenu()
143
- actions = self.get_common_actions(index)
210
+ actions = self.get_common_actions(index, column_idx)
144
211
  actions['refresh'] = QAction(QIcon(":/icons/reload.svg"), trans('action.refresh'), self)
145
212
  actions['refresh'].triggered.connect(
146
213
  lambda: self.window.controller.files.update_explorer()
@@ -149,51 +216,73 @@ class OutputTabs(QTabWidget):
149
216
  context_menu.addAction(actions['add_notepad'])
150
217
  context_menu.addAction(actions['refresh'])
151
218
  context_menu.addAction(actions['edit'])
219
+
220
+ # move
221
+ if column_idx != 0:
222
+ context_menu.addAction(actions['move_left'])
223
+ if column_idx != 1:
224
+ context_menu.addAction(actions['move_right'])
225
+
152
226
  context_menu.exec(global_pos)
153
227
 
154
- def show_default_menu(self, index, global_pos):
228
+ def show_default_menu(self, index: int, column_idx: int, global_pos):
155
229
  """
156
230
  Show default menu
157
231
 
158
232
  :param index: index
233
+ :param column_idx: column index
159
234
  :param global_pos: global position
160
235
  """
161
236
  context_menu = QMenu()
162
- actions = self.get_common_actions(index)
237
+ actions = self.get_common_actions(index, column_idx)
163
238
  context_menu.addAction(actions['add_chat'])
164
239
  context_menu.addAction(actions['add_notepad'])
165
240
  context_menu.addAction(actions['edit'])
241
+
242
+ # move
243
+ if column_idx != 0:
244
+ context_menu.addAction(actions['move_left'])
245
+ if column_idx != 1:
246
+ context_menu.addAction(actions['move_right'])
247
+
166
248
  context_menu.exec(global_pos)
167
249
 
168
250
  @Slot()
169
- def rename_tab(self, index):
251
+ def rename_tab(self, index: int, column_idx: int):
170
252
  """
171
253
  Rename tab
254
+
172
255
  :param index: index
256
+ :param column_idx: column index
173
257
  """
174
- self.window.controller.ui.tabs.rename(index)
258
+ self.window.controller.ui.tabs.rename(index, column_idx)
175
259
 
176
260
  @Slot()
177
- def close_tab(self, index):
261
+ def close_tab(self, index: int, column_idx: int):
178
262
  """
179
263
  Close tab
264
+
180
265
  :param index: index
266
+ :param column_idx: column index
181
267
  """
182
- self.window.controller.ui.tabs.close(index)
268
+ self.window.controller.ui.tabs.close(index, column_idx)
183
269
 
184
270
  @Slot()
185
- def close_all(self, type):
271
+ def close_all(self, type, column_idx: int):
186
272
  """
187
273
  Close all tabs
274
+
188
275
  :param type: type
276
+ :param column_idx: column index
189
277
  """
190
- self.window.controller.ui.tabs.close_all(type)
278
+ self.window.controller.ui.tabs.close_all(type, column_idx)
191
279
 
192
280
  @Slot()
193
- def add_tab(self, index, type):
281
+ def add_tab(self, index: int, column_idx: int, type):
194
282
  """
195
283
  Add tab
196
284
  :param index: index
285
+ :param column_idx: column index
197
286
  :param type: type
198
287
  """
199
- self.window.controller.ui.tabs.append(type, index)
288
+ self.window.controller.ui.tabs.append(type, index, column_idx)
@@ -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.20 21:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -42,6 +42,15 @@ class HtmlOutput(QWebEngineView):
42
42
  self.plain = ""
43
43
  self.html_content = ""
44
44
  self.meta = None
45
+ self.tab = None
46
+
47
+ def set_tab(self, tab):
48
+ """
49
+ Set tab
50
+
51
+ :param tab: Tab
52
+ """
53
+ self.tab = tab
45
54
 
46
55
  def set_meta(self, meta: CtxMeta):
47
56
  """
@@ -163,6 +172,7 @@ class HtmlOutput(QWebEngineView):
163
172
  if success:
164
173
  event = RenderEvent(RenderEvent.ON_PAGE_LOAD, {
165
174
  "meta": self.meta,
175
+ "tab": self.tab,
166
176
  })
167
177
  self.window.dispatch(event)
168
178
 
@@ -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.04.24 01:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -38,6 +38,15 @@ class ChatOutput(QTextBrowser):
38
38
  self.setOpenLinks(False)
39
39
  self.anchorClicked.connect(self.open_external_link)
40
40
  self.setWordWrapMode(QTextOption.WordWrap)
41
+ self.tab = None
42
+
43
+ def set_tab(self, tab):
44
+ """
45
+ Set tab
46
+
47
+ :param tab: Tab
48
+ """
49
+ self.tab = tab
41
50
 
42
51
  def open_external_link(self, url):
43
52
  """
@@ -6,21 +6,24 @@
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.20 21:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
13
13
 
14
- from PySide6.QtCore import Qt, QObject, Signal, Slot
14
+ from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent
15
15
  from PySide6.QtWebChannel import QWebChannel
16
16
  from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage
17
17
  from PySide6.QtWebEngineWidgets import QWebEngineView
18
18
  from PySide6.QtGui import QAction, QIcon, QKeySequence
19
+ from PySide6.QtWidgets import QMenu
19
20
 
20
21
  from pygpt_net.core.events import RenderEvent
21
22
  from pygpt_net.item.ctx import CtxMeta
22
23
  from pygpt_net.core.text.web_finder import WebFinder
24
+ from pygpt_net.ui.widget.tabs.layout import FocusEventFilter
23
25
  from pygpt_net.utils import trans
26
+
24
27
  import pygpt_net.icons_rc
25
28
 
26
29
 
@@ -38,9 +41,46 @@ class ChatWebOutput(QWebEngineView):
38
41
  self.customContextMenuRequested.connect(self.on_context_menu)
39
42
  self.signals = WebEngineSignals()
40
43
  self.setContextMenuPolicy(Qt.CustomContextMenu)
44
+ self.filter = FocusEventFilter(self, self.on_focus)
45
+ self.installEventFilter(self)
41
46
  self.plain = ""
42
47
  self.html_content = ""
43
48
  self.meta = None
49
+ self.tab = None
50
+
51
+ def eventFilter(self, source, event):
52
+ """
53
+ Focus event filter
54
+
55
+ :param source: source
56
+ :param event: event
57
+ """
58
+ if (event.type() == QEvent.ChildAdded and
59
+ source is self and
60
+ event.child().isWidgetType()):
61
+ self._glwidget = event.child()
62
+ self._glwidget.installEventFilter(self)
63
+ elif (event.type() == event.Type.MouseButtonPress):
64
+ col_idx = self.tab.column_idx
65
+ self.window.controller.ui.tabs.on_column_focus(col_idx)
66
+ return super().eventFilter(source, event)
67
+
68
+ def on_focus(self, widget):
69
+ """
70
+ On widget clicked
71
+
72
+ :param widget: widget
73
+ """
74
+ self.window.controller.ui.tabs.on_column_focus(self.tab.column_idx)
75
+ self.setFocus()
76
+
77
+ def set_tab(self, tab):
78
+ """
79
+ Set tab
80
+
81
+ :param tab: Tab
82
+ """
83
+ self.tab = tab
44
84
 
45
85
  def set_meta(self, meta: CtxMeta):
46
86
  """
@@ -72,12 +112,7 @@ class ChatWebOutput(QWebEngineView):
72
112
 
73
113
  :param position: position
74
114
  """
75
- menu = self.createStandardContextMenu()
76
-
77
- # remove defaults
78
- for action in menu.actions()[::-1]:
79
- menu.removeAction(action)
80
-
115
+ menu = QMenu(self)
81
116
  selected_text = ""
82
117
  is_selection = self.page().hasSelection()
83
118
  if is_selection:
@@ -138,6 +173,10 @@ class ChatWebOutput(QWebEngineView):
138
173
  if self.window.core.config.has("zoom"):
139
174
  self.page().setZoomFactor(self.window.core.config.get("zoom"))
140
175
 
176
+ def on_focus_js(self):
177
+ """Focus JavaScript"""
178
+ self.window.controller.ui.tabs.on_column_focus(self.tab.column_idx)
179
+
141
180
  def get_zoom_value(self) -> float:
142
181
  """
143
182
  Get zoom value
@@ -165,6 +204,7 @@ class ChatWebOutput(QWebEngineView):
165
204
  if success:
166
205
  event = RenderEvent(RenderEvent.ON_PAGE_LOAD, {
167
206
  "meta": self.meta,
207
+ "tab": self.tab,
168
208
  })
169
209
  self.window.dispatch(event)
170
210
 
@@ -318,4 +358,4 @@ class WebEngineSignals(QObject):
318
358
 
319
359
 
320
360
  class WebEnginePageSignals(QObject):
321
- js_message = Signal(int, str, str) # on Javascript message
361
+ js_message = Signal(int, str, str) # on Javascript message
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pygpt-net
3
- Version: 2.4.38
3
+ Version: 2.4.39
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
@@ -92,7 +92,7 @@ Description-Content-Type: text/markdown
92
92
 
93
93
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
94
94
 
95
- Release: **2.4.38** | build: **2024.12.08** | Python: **>=3.10, <3.12**
95
+ Release: **2.4.39** | build: **2024.12.09** | Python: **>=3.10, <3.12**
96
96
 
97
97
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
98
98
  >
@@ -3963,6 +3963,12 @@ may consume additional tokens that are not displayed in the main window.
3963
3963
 
3964
3964
  ## Recent changes:
3965
3965
 
3966
+ **2.4.39 (2024-12-09)**
3967
+
3968
+ - 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.
3969
+
3970
+ - Fixed: Language switch.
3971
+
3966
3972
  **2.4.38 (2024-12-08)**
3967
3973
 
3968
3974
  - 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...