pygpt-net 2.6.46__py3-none-any.whl → 2.6.48__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +9 -0
- pygpt_net/__init__.py +1 -1
- pygpt_net/app.py +5 -0
- pygpt_net/app_core.py +39 -39
- pygpt_net/controller/__init__.py +72 -64
- pygpt_net/controller/audio/audio.py +2 -0
- pygpt_net/controller/chat/text.py +2 -1
- pygpt_net/controller/ctx/common.py +0 -7
- pygpt_net/controller/ctx/ctx.py +172 -6
- pygpt_net/controller/ctx/extra.py +3 -3
- pygpt_net/controller/notepad/notepad.py +0 -2
- pygpt_net/controller/settings/editor.py +3 -1
- pygpt_net/controller/theme/common.py +8 -2
- pygpt_net/controller/theme/theme.py +5 -5
- pygpt_net/controller/ui/tabs.py +9 -2
- pygpt_net/core/ctx/ctx.py +79 -26
- pygpt_net/core/render/web/renderer.py +2 -0
- pygpt_net/core/tabs/tab.py +2 -2
- pygpt_net/core/tabs/tabs.py +57 -10
- pygpt_net/data/config/config.json +2 -2
- pygpt_net/data/config/models.json +2 -2
- pygpt_net/data/css/web-blocks.css +256 -270
- pygpt_net/data/css/web-chatgpt.css +276 -301
- pygpt_net/data/css/web-chatgpt_wide.css +286 -294
- pygpt_net/data/js/app.js +1218 -1186
- pygpt_net/js_rc.py +14192 -14641
- pygpt_net/provider/core/config/patch.py +9 -0
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +19 -5
- pygpt_net/ui/__init__.py +9 -14
- pygpt_net/ui/layout/chat/chat.py +2 -2
- pygpt_net/ui/layout/ctx/ctx_list.py +71 -1
- pygpt_net/ui/widget/lists/base.py +32 -1
- pygpt_net/ui/widget/lists/context.py +45 -2
- pygpt_net/ui/widget/tabs/body.py +11 -3
- pygpt_net/ui/widget/textarea/notepad.py +0 -4
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/METADATA +11 -2
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/RECORD +40 -40
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/entry_points.txt +0 -0
|
@@ -88,6 +88,15 @@ class Patch:
|
|
|
88
88
|
patch_css('web-chatgpt_wide.light.css', True)
|
|
89
89
|
updated = True
|
|
90
90
|
|
|
91
|
+
# < 2.6.48
|
|
92
|
+
if old < parse_version("2.6.48"):
|
|
93
|
+
print("Migrating config from < 2.6.48...")
|
|
94
|
+
# reformat
|
|
95
|
+
patch_css('web-chatgpt.css', True)
|
|
96
|
+
patch_css('web-chatgpt_wide.css', True)
|
|
97
|
+
patch_css('web-blocks.css', True)
|
|
98
|
+
updated = True
|
|
99
|
+
|
|
91
100
|
# update file
|
|
92
101
|
migrated = False
|
|
93
102
|
if updated:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2025.09.15 22:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from datetime import datetime
|
|
@@ -109,6 +109,12 @@ class Storage:
|
|
|
109
109
|
continue
|
|
110
110
|
mode = filter.get('mode', '=')
|
|
111
111
|
value = filter.get('value', '')
|
|
112
|
+
|
|
113
|
+
# handle special case for "ungrouped" (group_id IS NULL OR = 0)
|
|
114
|
+
if key == 'group_id' and str(mode).upper() == 'NULL_OR_ZERO':
|
|
115
|
+
where_clauses.append("(m.group_id IS NULL OR m.group_id = 0)")
|
|
116
|
+
continue
|
|
117
|
+
|
|
112
118
|
key_name = 'm.' + key
|
|
113
119
|
if isinstance(value, int):
|
|
114
120
|
where_clauses.append(f"{key_name} {mode} :{key}")
|
|
@@ -116,7 +122,7 @@ class Storage:
|
|
|
116
122
|
elif isinstance(value, str):
|
|
117
123
|
where_clauses.append(f"{key_name} {mode} :{key}")
|
|
118
124
|
bind_params[key] = f"%{value}%"
|
|
119
|
-
elif isinstance(value, list):
|
|
125
|
+
elif isinstance(value, list) and len(value) > 0:
|
|
120
126
|
values = "(" + ",".join([str(x) for x in value]) + ")"
|
|
121
127
|
where_clauses.append(f"{key_name} {mode} {values}")
|
|
122
128
|
|
|
@@ -148,15 +154,21 @@ class Storage:
|
|
|
148
154
|
:return: dict of CtxMeta
|
|
149
155
|
"""
|
|
150
156
|
limit_suffix = ""
|
|
151
|
-
if limit is not None and limit > 0:
|
|
152
|
-
limit_suffix = " LIMIT {}".format(limit)
|
|
153
|
-
|
|
154
157
|
where_statement, join_statement, bind_params = self.prepare_query(
|
|
155
158
|
search_string=search_string,
|
|
156
159
|
filters=filters,
|
|
157
160
|
search_content=search_content,
|
|
158
161
|
append_date_ranges=True,
|
|
159
162
|
)
|
|
163
|
+
|
|
164
|
+
# Build LIMIT/OFFSET only when limit > 0; LIMIT 0 would mean "no rows"
|
|
165
|
+
if limit is not None and int(limit) > 0:
|
|
166
|
+
limit_suffix = " LIMIT :limit"
|
|
167
|
+
bind_params['limit'] = int(limit)
|
|
168
|
+
if offset is not None and int(offset) > 0:
|
|
169
|
+
limit_suffix += " OFFSET :offset"
|
|
170
|
+
bind_params['offset'] = int(offset)
|
|
171
|
+
|
|
160
172
|
stmt_text = f"""
|
|
161
173
|
SELECT
|
|
162
174
|
m.*,
|
|
@@ -168,6 +180,8 @@ class Storage:
|
|
|
168
180
|
{join_statement}
|
|
169
181
|
WHERE
|
|
170
182
|
{where_statement}
|
|
183
|
+
GROUP BY
|
|
184
|
+
m.id
|
|
171
185
|
ORDER BY
|
|
172
186
|
m.updated_ts DESC {limit_suffix}
|
|
173
187
|
"""
|
pygpt_net/ui/__init__.py
CHANGED
|
@@ -16,13 +16,13 @@ from PySide6.QtCore import Qt, QTimer
|
|
|
16
16
|
from PySide6.QtGui import QFontDatabase, QIcon
|
|
17
17
|
from PySide6.QtWidgets import QSplitter, QMessageBox
|
|
18
18
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
19
|
+
from .base.context_menu import ContextMenu
|
|
20
|
+
from .dialogs import Dialogs
|
|
21
|
+
from .layout.chat import ChatMain
|
|
22
|
+
from .layout.ctx import CtxMain
|
|
23
|
+
from .layout.toolbox import ToolboxMain
|
|
24
|
+
from .menu import Menu
|
|
25
|
+
from .tray import Tray
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class UI:
|
|
@@ -139,12 +139,7 @@ class UI:
|
|
|
139
139
|
suffix = self.window.core.platforms.get_env_suffix()
|
|
140
140
|
profile_name = self.window.core.config.profile.get_current_name()
|
|
141
141
|
self.window.setWindowTitle(
|
|
142
|
-
|
|
143
|
-
self.window.meta['version'],
|
|
144
|
-
self.window.meta['build'].replace('.', '-'),
|
|
145
|
-
suffix,
|
|
146
|
-
profile_name,
|
|
147
|
-
)
|
|
142
|
+
f"PyGPT - Desktop AI Assistant {self.window.meta['version']} | build {self.window.meta['build'].replace('.', '-')}{suffix} ({profile_name})"
|
|
148
143
|
)
|
|
149
144
|
|
|
150
145
|
def post_setup(self):
|
|
@@ -164,7 +159,7 @@ class UI:
|
|
|
164
159
|
return
|
|
165
160
|
msg = str(text)
|
|
166
161
|
msg = msg.replace("\n", " ")
|
|
167
|
-
status = msg[:self.STATUS_MAX_CHARS]
|
|
162
|
+
status = f"{msg[:self.STATUS_MAX_CHARS]}..." if len(msg) > self.STATUS_MAX_CHARS else msg # truncate
|
|
168
163
|
self.nodes['status'].setText(status)
|
|
169
164
|
|
|
170
165
|
def get_status(self):
|
pygpt_net/ui/layout/chat/chat.py
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
from PySide6.QtCore import Qt, Slot
|
|
13
13
|
from PySide6.QtWidgets import QSplitter
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
15
|
+
from .input import Input
|
|
16
|
+
from .output import Output
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ChatMain:
|
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.15 22:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6 import QtCore
|
|
@@ -113,9 +113,79 @@ class CtxList:
|
|
|
113
113
|
self.update_items_pinned(id, data)
|
|
114
114
|
self.update_items(id, data)
|
|
115
115
|
self.update_groups(id, data, expand=expand)
|
|
116
|
+
|
|
117
|
+
# APPLY PENDING SCROLL BEFORE RE-ENABLING UPDATES (prevents top flicker)
|
|
118
|
+
try:
|
|
119
|
+
node.apply_pending_scroll()
|
|
120
|
+
node.clear_pending_scroll()
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
116
123
|
finally:
|
|
117
124
|
node.setUpdatesEnabled(True)
|
|
118
125
|
|
|
126
|
+
def _find_first_group_row(self, model) -> int:
|
|
127
|
+
"""Find the row index of the first GroupItem; return -1 if none."""
|
|
128
|
+
for r in range(model.rowCount()):
|
|
129
|
+
it = model.item(r)
|
|
130
|
+
if isinstance(it, GroupItem):
|
|
131
|
+
return r
|
|
132
|
+
return -1
|
|
133
|
+
|
|
134
|
+
def append_unpaginated(self, id: str, data: dict, add_ids: list[int]):
|
|
135
|
+
"""
|
|
136
|
+
Append more ungrouped and not pinned items without rebuilding the model.
|
|
137
|
+
Keeps scroll position perfectly stable.
|
|
138
|
+
"""
|
|
139
|
+
if not add_ids:
|
|
140
|
+
return
|
|
141
|
+
node = self.window.ui.nodes[id]
|
|
142
|
+
model = self.window.ui.models[id]
|
|
143
|
+
|
|
144
|
+
folders_top = bool(self.window.core.config.get("ctx.records.folders.top"))
|
|
145
|
+
# decide insertion point: at the end, or just before the first group row
|
|
146
|
+
insert_pos = model.rowCount()
|
|
147
|
+
if not folders_top:
|
|
148
|
+
grp_idx = self._find_first_group_row(model)
|
|
149
|
+
insert_pos = grp_idx if grp_idx >= 0 else model.rowCount()
|
|
150
|
+
|
|
151
|
+
# find last dt of existing ungrouped area before insertion point (for date sections)
|
|
152
|
+
last_dt_str = None
|
|
153
|
+
for r in range(insert_pos - 1, -1, -1):
|
|
154
|
+
it = model.item(r)
|
|
155
|
+
if isinstance(it, Item):
|
|
156
|
+
data_role = it.data(QtCore.Qt.ItemDataRole.UserRole) or {}
|
|
157
|
+
if not data_role.get("in_group", False) and not data_role.get("is_important", False):
|
|
158
|
+
last_dt_str = getattr(it, "dt", None)
|
|
159
|
+
break
|
|
160
|
+
elif isinstance(it, GroupItem):
|
|
161
|
+
break # hit groups boundary going upwards
|
|
162
|
+
else:
|
|
163
|
+
# SectionItem or others – skip
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
node.setUpdatesEnabled(False)
|
|
167
|
+
try:
|
|
168
|
+
# append strictly in the order provided by add_ids (older first)
|
|
169
|
+
for mid in add_ids:
|
|
170
|
+
meta = data.get(mid)
|
|
171
|
+
if meta is None:
|
|
172
|
+
continue
|
|
173
|
+
item = self.build_item(mid, meta, is_group=False)
|
|
174
|
+
|
|
175
|
+
# Optional date sections (same logic as in update_items)
|
|
176
|
+
if self._group_separators and (not item.isPinned or self._pinned_separators):
|
|
177
|
+
if last_dt_str is None or last_dt_str != item.dt:
|
|
178
|
+
section = self.build_date_section(item.dt, group=False)
|
|
179
|
+
if section:
|
|
180
|
+
model.insertRow(insert_pos, section)
|
|
181
|
+
insert_pos += 1
|
|
182
|
+
last_dt_str = item.dt
|
|
183
|
+
|
|
184
|
+
model.insertRow(insert_pos, item)
|
|
185
|
+
insert_pos += 1
|
|
186
|
+
finally:
|
|
187
|
+
node.setUpdatesEnabled(True)
|
|
188
|
+
|
|
119
189
|
def update_items(self, id, data):
|
|
120
190
|
"""
|
|
121
191
|
Update items
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.09.15 22:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import QItemSelectionModel
|
|
@@ -36,6 +36,10 @@ class BaseList(QTreeView):
|
|
|
36
36
|
self.v_scroll_value = 0
|
|
37
37
|
self.h_scroll_value = 0
|
|
38
38
|
|
|
39
|
+
# pending scroll values applied while updates are disabled (to avoid top flicker)
|
|
40
|
+
self._pending_v_scroll_value = None
|
|
41
|
+
self._pending_h_scroll_value = None
|
|
42
|
+
|
|
39
43
|
def click(self, val):
|
|
40
44
|
self.window.controller.mode.select(self.id)
|
|
41
45
|
self.selection = self.selectionModel().selection()
|
|
@@ -103,3 +107,30 @@ class BaseList(QTreeView):
|
|
|
103
107
|
"""Restore scroll position"""
|
|
104
108
|
self.verticalScrollBar().setValue(self.v_scroll_value)
|
|
105
109
|
self.horizontalScrollBar().setValue(self.h_scroll_value)
|
|
110
|
+
|
|
111
|
+
def set_pending_v_scroll(self, value: int):
|
|
112
|
+
"""
|
|
113
|
+
Set vertical scroll value to apply while updates are disabled.
|
|
114
|
+
This prevents a visible jump to the top during model rebuild.
|
|
115
|
+
"""
|
|
116
|
+
self._pending_v_scroll_value = int(value)
|
|
117
|
+
|
|
118
|
+
def set_pending_h_scroll(self, value: int):
|
|
119
|
+
"""Optional: set horizontal pending value."""
|
|
120
|
+
self._pending_h_scroll_value = int(value)
|
|
121
|
+
|
|
122
|
+
def clear_pending_scroll(self):
|
|
123
|
+
"""Clear pending scroll values."""
|
|
124
|
+
self._pending_v_scroll_value = None
|
|
125
|
+
self._pending_h_scroll_value = None
|
|
126
|
+
|
|
127
|
+
def apply_pending_scroll(self):
|
|
128
|
+
"""
|
|
129
|
+
Apply pending scroll values immediately.
|
|
130
|
+
IMPORTANT: Call this before re-enabling updates to avoid repaint at top.
|
|
131
|
+
"""
|
|
132
|
+
if self._pending_v_scroll_value is not None:
|
|
133
|
+
self.verticalScrollBar().setValue(self._pending_v_scroll_value)
|
|
134
|
+
if self._pending_h_scroll_value is not None:
|
|
135
|
+
self.horizontalScrollBar().setValue(self._pending_h_scroll_value)
|
|
136
|
+
# do not clear here; let caller decide when to clear
|
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.15 22:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
@@ -17,7 +17,7 @@ from PySide6.QtCore import Qt, QPoint, QItemSelectionModel
|
|
|
17
17
|
from PySide6.QtGui import QIcon, QColor, QPixmap, QStandardItem
|
|
18
18
|
from PySide6.QtWidgets import QMenu
|
|
19
19
|
|
|
20
|
-
from
|
|
20
|
+
from .base import BaseList
|
|
21
21
|
from pygpt_net.utils import trans
|
|
22
22
|
|
|
23
23
|
|
|
@@ -67,6 +67,30 @@ class ContextList(BaseList):
|
|
|
67
67
|
# Safe no-op if the underlying view does not support setIndentation
|
|
68
68
|
pass
|
|
69
69
|
|
|
70
|
+
self._loading_more = False # guard to avoid multiple triggers while updating
|
|
71
|
+
try:
|
|
72
|
+
self.verticalScrollBar().valueChanged.connect(self._on_vertical_scroll)
|
|
73
|
+
except Exception:
|
|
74
|
+
pass # safe no-op if view doesn't expose verticalScrollBar
|
|
75
|
+
|
|
76
|
+
def _on_vertical_scroll(self, value: int):
|
|
77
|
+
"""
|
|
78
|
+
Trigger infinite scroll: when scrollbar reaches bottom, request the next page.
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
sb = self.verticalScrollBar()
|
|
82
|
+
except Exception:
|
|
83
|
+
return
|
|
84
|
+
if sb.maximum() <= 0:
|
|
85
|
+
return # nothing to scroll
|
|
86
|
+
# Close-to-bottom detection; keep a tiny threshold for stability
|
|
87
|
+
if not self._loading_more and value >= sb.maximum():
|
|
88
|
+
self._loading_more = True
|
|
89
|
+
# Ask controller to increase the total limit and refresh the list
|
|
90
|
+
self.window.controller.ctx.load_more()
|
|
91
|
+
# Release the guard shortly after model updates
|
|
92
|
+
QtCore.QTimer.singleShot(250, lambda: setattr(self, "_loading_more", False))
|
|
93
|
+
|
|
70
94
|
@property
|
|
71
95
|
def _model(self):
|
|
72
96
|
return self.window.ui.models['ctx.list']
|
|
@@ -292,6 +316,25 @@ class ContextList(BaseList):
|
|
|
292
316
|
self.restore_after_ctx_menu = True
|
|
293
317
|
self.restore_scroll_position()
|
|
294
318
|
|
|
319
|
+
def get_visible_unpaged_ids(self) -> set:
|
|
320
|
+
"""
|
|
321
|
+
Return a set of IDs for currently visible, ungrouped and not pinned items (top-level only).
|
|
322
|
+
"""
|
|
323
|
+
ids = set()
|
|
324
|
+
model = self._model
|
|
325
|
+
for r in range(model.rowCount()):
|
|
326
|
+
it = model.item(r)
|
|
327
|
+
# skip groups and date sections
|
|
328
|
+
if isinstance(it, GroupItem) or isinstance(it, SectionItem):
|
|
329
|
+
continue
|
|
330
|
+
if isinstance(it, Item):
|
|
331
|
+
data = it.data(QtCore.Qt.ItemDataRole.UserRole) or {}
|
|
332
|
+
in_group = bool(data.get("in_group", False))
|
|
333
|
+
is_important = bool(data.get("is_important", False))
|
|
334
|
+
if not in_group and not is_important and hasattr(it, "id"):
|
|
335
|
+
ids.add(int(it.id))
|
|
336
|
+
return ids
|
|
337
|
+
|
|
295
338
|
def action_open(self, id: int, idx: int = None):
|
|
296
339
|
"""
|
|
297
340
|
Open context action handler
|
pygpt_net/ui/widget/tabs/body.py
CHANGED
|
@@ -31,7 +31,7 @@ class TabBody(QTabWidget):
|
|
|
31
31
|
"""
|
|
32
32
|
Clean up on delete
|
|
33
33
|
"""
|
|
34
|
-
if self.on_delete:
|
|
34
|
+
if self.on_delete and callable(self.on_delete):
|
|
35
35
|
self.on_delete(self)
|
|
36
36
|
self.delete_refs()
|
|
37
37
|
|
|
@@ -49,10 +49,18 @@ class TabBody(QTabWidget):
|
|
|
49
49
|
Delete all references to widgets in this tab
|
|
50
50
|
"""
|
|
51
51
|
for ref in self.refs:
|
|
52
|
+
if ref is None:
|
|
53
|
+
continue
|
|
52
54
|
if ref and hasattr(ref, 'on_delete'):
|
|
53
|
-
|
|
55
|
+
try:
|
|
56
|
+
ref.on_delete()
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
54
59
|
if ref and hasattr(ref, 'deleteLater'):
|
|
55
|
-
|
|
60
|
+
try:
|
|
61
|
+
ref.deleteLater()
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
56
64
|
del self.refs[:]
|
|
57
65
|
|
|
58
66
|
def delete_ref(self, widget: Any) -> None:
|
|
@@ -120,10 +120,6 @@ class NotepadOutput(QTextEdit):
|
|
|
120
120
|
if self.finder:
|
|
121
121
|
self.finder.disconnect() # disconnect finder
|
|
122
122
|
self.finder = None # delete finder
|
|
123
|
-
try:
|
|
124
|
-
self._vscroll.valueChanged.disconnect(self._on_scrollbar_value_changed)
|
|
125
|
-
except Exception:
|
|
126
|
-
pass
|
|
127
123
|
self.deleteLater()
|
|
128
124
|
|
|
129
125
|
def showEvent(self, event):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pygpt-net
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.48
|
|
4
4
|
Summary: Desktop AI Assistant powered by: OpenAI GPT-5, GPT-4, o1, o3, 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: ai,api,api key,app,assistant,bielik,chat,chatbot,chatgpt,claude,dall-e,deepseek,desktop,gemini,gpt,gpt-3.5,gpt-4,gpt-4-vision,gpt-4o,gpt-5,gpt-oss,gpt3.5,gpt4,grok,langchain,llama-index,llama3,mistral,o1,o3,ollama,openai,presets,py-gpt,py_gpt,pygpt,pyside,qt,text completion,tts,ui,vision,whisper
|
|
@@ -118,7 +118,7 @@ Description-Content-Type: text/markdown
|
|
|
118
118
|
|
|
119
119
|
[](https://snapcraft.io/pygpt)
|
|
120
120
|
|
|
121
|
-
Release: **2.6.
|
|
121
|
+
Release: **2.6.48** | build: **2025-09-15** | Python: **>=3.10, <3.14**
|
|
122
122
|
|
|
123
123
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
|
124
124
|
>
|
|
@@ -3612,6 +3612,15 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3612
3612
|
|
|
3613
3613
|
## Recent changes:
|
|
3614
3614
|
|
|
3615
|
+
**2.6.48 (2025-09-15)**
|
|
3616
|
+
|
|
3617
|
+
- Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
|
|
3618
|
+
|
|
3619
|
+
**2.6.47 (2025-09-15)**
|
|
3620
|
+
|
|
3621
|
+
- Improved: Parsing of custom markup tags.
|
|
3622
|
+
- Optimized: Switching profiles.
|
|
3623
|
+
|
|
3615
3624
|
**2.6.46 (2025-09-15)**
|
|
3616
3625
|
|
|
3617
3626
|
- Added: Global proxy settings for all API SDKs.
|