pygpt-net 2.7.8__py3-none-any.whl → 2.7.10__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 +14 -0
- pygpt_net/LICENSE +1 -1
- pygpt_net/__init__.py +3 -3
- pygpt_net/config.py +15 -1
- pygpt_net/controller/chat/common.py +5 -4
- pygpt_net/controller/chat/image.py +3 -3
- pygpt_net/controller/chat/stream.py +76 -41
- pygpt_net/controller/chat/stream_worker.py +3 -3
- pygpt_net/controller/ctx/extra.py +3 -1
- pygpt_net/controller/dialogs/debug.py +37 -8
- pygpt_net/controller/kernel/kernel.py +3 -7
- pygpt_net/controller/lang/custom.py +25 -12
- pygpt_net/controller/lang/lang.py +45 -3
- pygpt_net/controller/lang/mapping.py +15 -2
- pygpt_net/controller/notepad/notepad.py +68 -25
- pygpt_net/controller/presets/editor.py +5 -1
- pygpt_net/controller/presets/presets.py +17 -5
- pygpt_net/controller/realtime/realtime.py +13 -1
- pygpt_net/controller/theme/theme.py +11 -2
- pygpt_net/controller/ui/tabs.py +1 -1
- pygpt_net/core/ctx/output.py +38 -12
- pygpt_net/core/db/database.py +4 -2
- pygpt_net/core/debug/console/console.py +30 -2
- pygpt_net/core/debug/context.py +2 -1
- pygpt_net/core/debug/ui.py +26 -4
- pygpt_net/core/filesystem/filesystem.py +6 -2
- pygpt_net/core/notepad/notepad.py +2 -2
- pygpt_net/core/tabs/tabs.py +79 -19
- pygpt_net/data/config/config.json +4 -3
- pygpt_net/data/config/models.json +37 -22
- pygpt_net/data/config/settings.json +12 -0
- pygpt_net/data/locale/locale.ar.ini +1833 -0
- pygpt_net/data/locale/locale.bg.ini +1833 -0
- pygpt_net/data/locale/locale.cs.ini +1833 -0
- pygpt_net/data/locale/locale.da.ini +1833 -0
- pygpt_net/data/locale/locale.de.ini +4 -1
- pygpt_net/data/locale/locale.en.ini +70 -67
- pygpt_net/data/locale/locale.es.ini +4 -1
- pygpt_net/data/locale/locale.fi.ini +1833 -0
- pygpt_net/data/locale/locale.fr.ini +4 -1
- pygpt_net/data/locale/locale.he.ini +1833 -0
- pygpt_net/data/locale/locale.hi.ini +1833 -0
- pygpt_net/data/locale/locale.hu.ini +1833 -0
- pygpt_net/data/locale/locale.it.ini +4 -1
- pygpt_net/data/locale/locale.ja.ini +1833 -0
- pygpt_net/data/locale/locale.ko.ini +1833 -0
- pygpt_net/data/locale/locale.nl.ini +1833 -0
- pygpt_net/data/locale/locale.no.ini +1833 -0
- pygpt_net/data/locale/locale.pl.ini +5 -2
- pygpt_net/data/locale/locale.pt.ini +1833 -0
- pygpt_net/data/locale/locale.ro.ini +1833 -0
- pygpt_net/data/locale/locale.ru.ini +1833 -0
- pygpt_net/data/locale/locale.sk.ini +1833 -0
- pygpt_net/data/locale/locale.sv.ini +1833 -0
- pygpt_net/data/locale/locale.tr.ini +1833 -0
- pygpt_net/data/locale/locale.uk.ini +4 -1
- pygpt_net/data/locale/locale.zh.ini +4 -1
- pygpt_net/item/notepad.py +8 -2
- pygpt_net/migrations/Version20260121190000.py +25 -0
- pygpt_net/migrations/Version20260122140000.py +25 -0
- pygpt_net/migrations/__init__.py +5 -1
- pygpt_net/preload.py +246 -3
- pygpt_net/provider/api/__init__.py +16 -2
- pygpt_net/provider/api/anthropic/__init__.py +21 -7
- pygpt_net/provider/api/google/__init__.py +21 -7
- pygpt_net/provider/api/google/image.py +89 -2
- pygpt_net/provider/api/google/realtime/client.py +70 -24
- pygpt_net/provider/api/google/realtime/realtime.py +48 -12
- pygpt_net/provider/api/google/video.py +2 -2
- pygpt_net/provider/api/openai/__init__.py +26 -11
- pygpt_net/provider/api/openai/image.py +79 -3
- pygpt_net/provider/api/openai/realtime/realtime.py +26 -6
- pygpt_net/provider/api/openai/responses.py +11 -31
- pygpt_net/provider/api/openai/video.py +2 -2
- pygpt_net/provider/api/x_ai/__init__.py +21 -10
- pygpt_net/provider/api/x_ai/realtime/client.py +185 -146
- pygpt_net/provider/api/x_ai/realtime/realtime.py +30 -15
- pygpt_net/provider/api/x_ai/remote_tools.py +83 -0
- pygpt_net/provider/api/x_ai/tools.py +51 -0
- pygpt_net/provider/core/config/patch.py +12 -1
- pygpt_net/provider/core/model/patch.py +36 -1
- pygpt_net/provider/core/notepad/db_sqlite/storage.py +53 -10
- pygpt_net/tools/agent_builder/ui/dialogs.py +2 -1
- pygpt_net/tools/audio_transcriber/ui/dialogs.py +2 -1
- pygpt_net/tools/code_interpreter/ui/dialogs.py +2 -1
- pygpt_net/tools/html_canvas/ui/dialogs.py +2 -1
- pygpt_net/tools/image_viewer/ui/dialogs.py +3 -5
- pygpt_net/tools/indexer/ui/dialogs.py +2 -1
- pygpt_net/tools/media_player/ui/dialogs.py +2 -1
- pygpt_net/tools/translator/ui/dialogs.py +2 -1
- pygpt_net/tools/translator/ui/widgets.py +6 -2
- pygpt_net/ui/dialog/about.py +2 -2
- pygpt_net/ui/dialog/db.py +2 -1
- pygpt_net/ui/dialog/debug.py +169 -6
- pygpt_net/ui/dialog/logger.py +6 -2
- pygpt_net/ui/dialog/models.py +36 -3
- pygpt_net/ui/dialog/preset.py +5 -1
- pygpt_net/ui/dialog/remote_store.py +2 -1
- pygpt_net/ui/main.py +3 -2
- pygpt_net/ui/widget/dialog/editor_file.py +2 -1
- pygpt_net/ui/widget/lists/debug.py +12 -7
- pygpt_net/ui/widget/option/checkbox.py +2 -8
- pygpt_net/ui/widget/option/combo.py +10 -2
- pygpt_net/ui/widget/textarea/console.py +156 -7
- pygpt_net/ui/widget/textarea/highlight.py +66 -0
- pygpt_net/ui/widget/textarea/input.py +624 -57
- pygpt_net/ui/widget/textarea/notepad.py +294 -27
- {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/LICENSE +1 -1
- {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/METADATA +16 -64
- {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/RECORD +112 -91
- {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/entry_points.txt +0 -0
|
@@ -6,17 +6,25 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.22 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt, QEvent, QTimer
|
|
13
|
-
from PySide6.QtGui import
|
|
13
|
+
from PySide6.QtGui import (
|
|
14
|
+
QAction,
|
|
15
|
+
QIcon,
|
|
16
|
+
QKeySequence,
|
|
17
|
+
QTextCursor,
|
|
18
|
+
QFontMetrics,
|
|
19
|
+
QColor,
|
|
20
|
+
)
|
|
14
21
|
from PySide6.QtWidgets import QTextEdit, QWidget, QVBoxLayout
|
|
15
22
|
|
|
16
23
|
from pygpt_net.core.tabs.tab import Tab
|
|
17
24
|
from pygpt_net.core.text.finder import Finder
|
|
18
25
|
from pygpt_net.ui.widget.element.labels import HelpLabel
|
|
19
26
|
from pygpt_net.utils import trans
|
|
27
|
+
from .highlight import MarkerHighlighter
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
class NotepadWidget(QWidget):
|
|
@@ -75,6 +83,7 @@ class NotepadWidget(QWidget):
|
|
|
75
83
|
"""On destroy"""
|
|
76
84
|
# unregister finder from memory
|
|
77
85
|
self.window.controller.finder.unset(self.textarea.finder)
|
|
86
|
+
|
|
78
87
|
def on_delete(self):
|
|
79
88
|
"""On delete"""
|
|
80
89
|
self.tab = None # clear tab reference
|
|
@@ -85,6 +94,8 @@ class NotepadOutput(QTextEdit):
|
|
|
85
94
|
ICON_VOLUME = QIcon(":/icons/volume.svg")
|
|
86
95
|
ICON_SAVE = QIcon(":/icons/save.svg")
|
|
87
96
|
ICON_SEARCH = QIcon(":/icons/search.svg")
|
|
97
|
+
ICON_MARK = QIcon(":/icons/edit.svg")
|
|
98
|
+
ICON_UNMARK = QIcon(":/icons/close.svg")
|
|
88
99
|
|
|
89
100
|
def __init__(self, window=None):
|
|
90
101
|
"""
|
|
@@ -106,6 +117,7 @@ class NotepadOutput(QTextEdit):
|
|
|
106
117
|
self.last_scroll_pos = None
|
|
107
118
|
self.installEventFilter(self)
|
|
108
119
|
self.setProperty('class', 'layout-notepad')
|
|
120
|
+
self.initialized = False
|
|
109
121
|
|
|
110
122
|
metrics = QFontMetrics(self.font())
|
|
111
123
|
space_width = metrics.horizontalAdvance(" ")
|
|
@@ -114,20 +126,47 @@ class NotepadOutput(QTextEdit):
|
|
|
114
126
|
self._vscroll = self.verticalScrollBar()
|
|
115
127
|
self._vscroll.valueChanged.connect(self._on_scrollbar_value_changed)
|
|
116
128
|
self._restore_attempts = 0
|
|
129
|
+
self._pending_scroll_pos = None
|
|
130
|
+
|
|
131
|
+
# highlight state (using QSyntaxHighlighter for rendering)
|
|
132
|
+
self._highlights = [] # list of (start, length)
|
|
133
|
+
|
|
134
|
+
# timers/slots must be available even if later connections fail
|
|
135
|
+
self._save_timer = QTimer(self)
|
|
136
|
+
self._save_timer.setSingleShot(True)
|
|
137
|
+
self._save_timer.setInterval(400)
|
|
138
|
+
self._save_timer.timeout.connect(self._persist)
|
|
139
|
+
|
|
140
|
+
# highlighter
|
|
141
|
+
self._highlighter = MarkerHighlighter(self.document(), self.get_highlights, self.get_highlight_color)
|
|
117
142
|
|
|
118
143
|
def on_delete(self):
|
|
119
144
|
"""On delete"""
|
|
120
145
|
if self.finder:
|
|
121
146
|
self.finder.disconnect() # disconnect finder
|
|
122
147
|
self.finder = None # delete finder
|
|
148
|
+
if self._save_timer.isActive():
|
|
149
|
+
self._save_timer.stop()
|
|
123
150
|
self.deleteLater()
|
|
124
151
|
|
|
125
152
|
def showEvent(self, event):
|
|
153
|
+
"""On show event"""
|
|
126
154
|
super().showEvent(event)
|
|
127
155
|
self._restore_attempts = 0
|
|
128
156
|
QTimer.singleShot(0, self.restore_scroll_pos)
|
|
157
|
+
self.initialized = True
|
|
158
|
+
|
|
159
|
+
def changeEvent(self, event):
|
|
160
|
+
"""React to theme/palette changes"""
|
|
161
|
+
if event.type() in (QEvent.PaletteChange, QEvent.ApplicationPaletteChange, QEvent.StyleChange):
|
|
162
|
+
try:
|
|
163
|
+
self._highlighter.rehighlight()
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
super().changeEvent(event)
|
|
129
167
|
|
|
130
168
|
def scroll_to_bottom(self):
|
|
169
|
+
"""Scroll to bottom"""
|
|
131
170
|
self.moveCursor(QTextCursor.End)
|
|
132
171
|
self.ensureCursorVisible()
|
|
133
172
|
scroll_bar = self._vscroll
|
|
@@ -155,26 +194,59 @@ class NotepadOutput(QTextEdit):
|
|
|
155
194
|
self.tab = tab
|
|
156
195
|
|
|
157
196
|
def setText(self, text: str):
|
|
197
|
+
"""
|
|
198
|
+
Set text
|
|
199
|
+
|
|
200
|
+
:param text: Text
|
|
201
|
+
"""
|
|
158
202
|
if self.toPlainText() == text:
|
|
159
203
|
return
|
|
160
204
|
self.setPlainText(text)
|
|
205
|
+
self._highlighter.rehighlight() # refresh highlighting on new content
|
|
161
206
|
|
|
162
207
|
def text_changed(self):
|
|
163
208
|
"""On text changed"""
|
|
164
209
|
if not self.window.core.notepad.locked:
|
|
165
|
-
self.
|
|
166
|
-
|
|
167
|
-
self.
|
|
168
|
-
|
|
210
|
+
if self.finder is not None:
|
|
211
|
+
self.finder.text_changed()
|
|
212
|
+
self.last_scroll_pos = self._vscroll.value()
|
|
213
|
+
if self.initialized and not self.toPlainText():
|
|
214
|
+
QTimer.singleShot(0, lambda: self.clear_highlights(persist=False)) # if empty, reset highlights
|
|
215
|
+
self.schedule_save()
|
|
169
216
|
|
|
170
217
|
def _on_scrollbar_value_changed(self, value: int):
|
|
171
|
-
|
|
218
|
+
"""
|
|
219
|
+
On scrollbar value changed
|
|
220
|
+
|
|
221
|
+
:param value: New value
|
|
222
|
+
"""
|
|
223
|
+
if not self.window.core.notepad.locked:
|
|
224
|
+
self.last_scroll_pos = value
|
|
225
|
+
self.schedule_save()
|
|
226
|
+
|
|
227
|
+
def is_initialized(self) -> bool:
|
|
228
|
+
"""
|
|
229
|
+
Check if initialized
|
|
230
|
+
|
|
231
|
+
:return: True if initialized
|
|
232
|
+
"""
|
|
233
|
+
return self.initialized
|
|
172
234
|
|
|
173
235
|
def restore_scroll_pos(self):
|
|
236
|
+
"""Restore last scroll position"""
|
|
237
|
+
if self.window.core.notepad.locked:
|
|
238
|
+
QTimer.singleShot(25, self.restore_scroll_pos)
|
|
239
|
+
return
|
|
240
|
+
if not self.initialized:
|
|
241
|
+
return
|
|
242
|
+
if self._pending_scroll_pos is not None:
|
|
243
|
+
self.last_scroll_pos = self._pending_scroll_pos
|
|
174
244
|
if self.last_scroll_pos is None:
|
|
175
245
|
return
|
|
176
246
|
scroll_bar = self._vscroll
|
|
177
247
|
current_max = scroll_bar.maximum()
|
|
248
|
+
if current_max == 0:
|
|
249
|
+
return # nothing to scroll
|
|
178
250
|
if self.last_scroll_pos > current_max:
|
|
179
251
|
if self._restore_attempts < 30:
|
|
180
252
|
self._restore_attempts += 1
|
|
@@ -182,7 +254,35 @@ class NotepadOutput(QTextEdit):
|
|
|
182
254
|
else:
|
|
183
255
|
scroll_bar.setValue(current_max)
|
|
184
256
|
else:
|
|
257
|
+
self.window.core.notepad.locked = True
|
|
185
258
|
scroll_bar.setValue(self.last_scroll_pos)
|
|
259
|
+
if self._pending_scroll_pos is not None:
|
|
260
|
+
self._pending_scroll_pos = None
|
|
261
|
+
self.window.core.notepad.locked = False
|
|
262
|
+
|
|
263
|
+
def set_scroll_pos(self, pos: int):
|
|
264
|
+
"""
|
|
265
|
+
Set scroll position
|
|
266
|
+
|
|
267
|
+
:param pos: Scroll position
|
|
268
|
+
"""
|
|
269
|
+
self._pending_scroll_pos = pos
|
|
270
|
+
self.last_scroll_pos = pos
|
|
271
|
+
|
|
272
|
+
def get_scroll_pos(self) -> int:
|
|
273
|
+
"""
|
|
274
|
+
Get scroll position
|
|
275
|
+
|
|
276
|
+
:return: Scroll position
|
|
277
|
+
"""
|
|
278
|
+
return self._vscroll.value()
|
|
279
|
+
|
|
280
|
+
def schedule_save(self):
|
|
281
|
+
"""Schedule save of notepad content"""
|
|
282
|
+
try:
|
|
283
|
+
self._save_timer.start()
|
|
284
|
+
except Exception:
|
|
285
|
+
pass
|
|
186
286
|
|
|
187
287
|
def contextMenuEvent(self, event):
|
|
188
288
|
"""
|
|
@@ -193,6 +293,23 @@ class NotepadOutput(QTextEdit):
|
|
|
193
293
|
menu = self.createStandardContextMenu()
|
|
194
294
|
cursor = self.textCursor()
|
|
195
295
|
selected_text = cursor.selectedText()
|
|
296
|
+
has_selection = bool(selected_text)
|
|
297
|
+
|
|
298
|
+
# Mark / Unmark actions for selection
|
|
299
|
+
if has_selection:
|
|
300
|
+
start = min(cursor.selectionStart(), cursor.selectionEnd())
|
|
301
|
+
end = max(cursor.selectionStart(), cursor.selectionEnd())
|
|
302
|
+
overlap = self._selection_overlaps_any_highlight(start, end)
|
|
303
|
+
|
|
304
|
+
action_mark = QAction(self.ICON_MARK, trans("action.mark"), self)
|
|
305
|
+
action_mark.triggered.connect(self.mark_selection)
|
|
306
|
+
menu.addAction(action_mark)
|
|
307
|
+
|
|
308
|
+
action_unmark = QAction(self.ICON_UNMARK, trans("action.unmark"), self)
|
|
309
|
+
action_unmark.setEnabled(overlap)
|
|
310
|
+
action_unmark.triggered.connect(self.unmark_selection)
|
|
311
|
+
menu.addAction(action_unmark)
|
|
312
|
+
|
|
196
313
|
if selected_text:
|
|
197
314
|
plain_text = cursor.selection().toPlainText()
|
|
198
315
|
|
|
@@ -239,6 +356,25 @@ class NotepadOutput(QTextEdit):
|
|
|
239
356
|
"""On content update"""
|
|
240
357
|
self.finder.clear() # clear finder
|
|
241
358
|
|
|
359
|
+
def on_zoom_changed(self, value: int):
|
|
360
|
+
"""
|
|
361
|
+
On font size changed
|
|
362
|
+
|
|
363
|
+
:param value: New font size
|
|
364
|
+
"""
|
|
365
|
+
self.value = value
|
|
366
|
+
self.window.core.config.data['font_size'] = value
|
|
367
|
+
self.window.core.config.save()
|
|
368
|
+
option = self.window.controller.settings.editor.get_option('font_size')
|
|
369
|
+
option['value'] = self.value
|
|
370
|
+
self.window.controller.config.apply(
|
|
371
|
+
parent_id='config',
|
|
372
|
+
key='font_size',
|
|
373
|
+
option=option,
|
|
374
|
+
)
|
|
375
|
+
self.window.controller.ui.update_font_size()
|
|
376
|
+
self.last_scroll_pos = self._vscroll.value()
|
|
377
|
+
|
|
242
378
|
def keyPressEvent(self, e):
|
|
243
379
|
"""
|
|
244
380
|
Key press event
|
|
@@ -286,25 +422,6 @@ class NotepadOutput(QTextEdit):
|
|
|
286
422
|
super(NotepadOutput, self).wheelEvent(event)
|
|
287
423
|
self.last_scroll_pos = self._vscroll.value()
|
|
288
424
|
|
|
289
|
-
def on_zoom_changed(self, value: int):
|
|
290
|
-
"""
|
|
291
|
-
On font size changed
|
|
292
|
-
|
|
293
|
-
:param value: New font size
|
|
294
|
-
"""
|
|
295
|
-
self.value = value
|
|
296
|
-
self.window.core.config.data['font_size'] = value
|
|
297
|
-
self.window.core.config.save()
|
|
298
|
-
option = self.window.controller.settings.editor.get_option('font_size')
|
|
299
|
-
option['value'] = self.value
|
|
300
|
-
self.window.controller.config.apply(
|
|
301
|
-
parent_id='config',
|
|
302
|
-
key='font_size',
|
|
303
|
-
option=option,
|
|
304
|
-
)
|
|
305
|
-
self.window.controller.ui.update_font_size()
|
|
306
|
-
self.last_scroll_pos = self._vscroll.value()
|
|
307
|
-
|
|
308
425
|
def focusInEvent(self, e):
|
|
309
426
|
"""
|
|
310
427
|
Focus in event
|
|
@@ -321,4 +438,154 @@ class NotepadOutput(QTextEdit):
|
|
|
321
438
|
:param e: focus event
|
|
322
439
|
"""
|
|
323
440
|
super(NotepadOutput, self).focusOutEvent(e)
|
|
324
|
-
self.window.controller.finder.focus_out(self.finder)
|
|
441
|
+
self.window.controller.finder.focus_out(self.finder)
|
|
442
|
+
|
|
443
|
+
# ==== Marking / Highlights API ====
|
|
444
|
+
|
|
445
|
+
def mark_selection(self):
|
|
446
|
+
"""Apply highlight to current selection"""
|
|
447
|
+
cursor = self.textCursor()
|
|
448
|
+
if not cursor.hasSelection():
|
|
449
|
+
return
|
|
450
|
+
start = min(cursor.selectionStart(), cursor.selectionEnd())
|
|
451
|
+
end = max(cursor.selectionStart(), cursor.selectionEnd())
|
|
452
|
+
self._add_highlight((start, end - start))
|
|
453
|
+
self._highlighter.rehighlight()
|
|
454
|
+
self._persist()
|
|
455
|
+
|
|
456
|
+
def unmark_selection(self):
|
|
457
|
+
"""Remove highlight from current selection"""
|
|
458
|
+
cursor = self.textCursor()
|
|
459
|
+
if not cursor.hasSelection():
|
|
460
|
+
return
|
|
461
|
+
start = min(cursor.selectionStart(), cursor.selectionEnd())
|
|
462
|
+
end = max(cursor.selectionStart(), cursor.selectionEnd())
|
|
463
|
+
self._remove_range_from_highlights(start, end - start)
|
|
464
|
+
self._highlighter.rehighlight()
|
|
465
|
+
self._persist()
|
|
466
|
+
|
|
467
|
+
def get_highlights(self):
|
|
468
|
+
"""Return current highlights as list of (start, length)"""
|
|
469
|
+
return list(self._highlights)
|
|
470
|
+
|
|
471
|
+
def set_highlights(self, highlights):
|
|
472
|
+
"""Set highlights and repaint"""
|
|
473
|
+
self._highlights = self._merge_ranges(self._sanitize_ranges(highlights))
|
|
474
|
+
self._highlighter.rehighlight()
|
|
475
|
+
|
|
476
|
+
def clear_highlights(self, persist: bool = True):
|
|
477
|
+
"""Clear all highlights"""
|
|
478
|
+
self._highlights = []
|
|
479
|
+
self._highlighter.rehighlight()
|
|
480
|
+
if persist:
|
|
481
|
+
self._persist()
|
|
482
|
+
|
|
483
|
+
# ==== Highlight colors / theme ====
|
|
484
|
+
|
|
485
|
+
def get_highlight_color(self):
|
|
486
|
+
"""
|
|
487
|
+
Return (text_color, background_color) for highlights based on current theme.
|
|
488
|
+
"""
|
|
489
|
+
is_dark = self._is_dark_theme()
|
|
490
|
+
if is_dark:
|
|
491
|
+
text_color = QColor(0, 0, 0)
|
|
492
|
+
bg_color = QColor(255, 255, 0) # yellow
|
|
493
|
+
else:
|
|
494
|
+
text_color = QColor(0, 0, 0)
|
|
495
|
+
bg_color = QColor(255, 255, 0) # yellow
|
|
496
|
+
return text_color, bg_color
|
|
497
|
+
|
|
498
|
+
def apply_highlight_theme(self):
|
|
499
|
+
"""
|
|
500
|
+
Public method to refresh highlight colors. Can be called by theme controller
|
|
501
|
+
after theme switch.
|
|
502
|
+
"""
|
|
503
|
+
try:
|
|
504
|
+
self._highlighter.rehighlight()
|
|
505
|
+
except Exception:
|
|
506
|
+
pass
|
|
507
|
+
|
|
508
|
+
def _is_dark_theme(self) -> bool:
|
|
509
|
+
"""
|
|
510
|
+
Get whether current theme is dark
|
|
511
|
+
"""
|
|
512
|
+
return self.window.controller.theme.is_dark_theme()
|
|
513
|
+
|
|
514
|
+
# ==== Internal highlight helpers ====
|
|
515
|
+
|
|
516
|
+
def _persist(self):
|
|
517
|
+
"""Persist notepad state"""
|
|
518
|
+
if self._save_timer.isActive():
|
|
519
|
+
self._save_timer.stop()
|
|
520
|
+
try:
|
|
521
|
+
self.window.controller.notepad.save(self.id) # save content + marking
|
|
522
|
+
except Exception as e:
|
|
523
|
+
print(e)
|
|
524
|
+
|
|
525
|
+
def _sanitize_ranges(self, ranges):
|
|
526
|
+
"""Sanitize ranges to (start>=0, length>0) integers"""
|
|
527
|
+
out = []
|
|
528
|
+
for r in ranges or []:
|
|
529
|
+
try:
|
|
530
|
+
s = int(r[0])
|
|
531
|
+
l = int(r[1])
|
|
532
|
+
except Exception:
|
|
533
|
+
continue
|
|
534
|
+
if s < 0 or l <= 0:
|
|
535
|
+
continue
|
|
536
|
+
out.append((s, l))
|
|
537
|
+
out.sort(key=lambda x: x[0])
|
|
538
|
+
return out
|
|
539
|
+
|
|
540
|
+
def _merge_ranges(self, ranges):
|
|
541
|
+
"""Merge overlapping/adjacent ranges"""
|
|
542
|
+
if not ranges:
|
|
543
|
+
return []
|
|
544
|
+
merged = []
|
|
545
|
+
for s, l in ranges:
|
|
546
|
+
if not merged:
|
|
547
|
+
merged.append([s, s + l])
|
|
548
|
+
continue
|
|
549
|
+
ps, pe = merged[-1]
|
|
550
|
+
se = s + l
|
|
551
|
+
if s <= pe:
|
|
552
|
+
merged[-1][1] = max(pe, se)
|
|
553
|
+
else:
|
|
554
|
+
merged.append([s, se])
|
|
555
|
+
return [(s, e - s) for s, e in merged]
|
|
556
|
+
|
|
557
|
+
def _add_highlight(self, rng):
|
|
558
|
+
"""Add a highlight range and merge"""
|
|
559
|
+
s, l = int(rng[0]), int(rng[1])
|
|
560
|
+
if l <= 0:
|
|
561
|
+
return
|
|
562
|
+
self._highlights.append((s, l))
|
|
563
|
+
self._highlights = self._merge_ranges(self._sanitize_ranges(self._highlights))
|
|
564
|
+
self.schedule_save()
|
|
565
|
+
|
|
566
|
+
def _remove_range_from_highlights(self, s, l):
|
|
567
|
+
"""Subtract a range from all highlights"""
|
|
568
|
+
if l <= 0:
|
|
569
|
+
return
|
|
570
|
+
start = s
|
|
571
|
+
end = s + l
|
|
572
|
+
result = []
|
|
573
|
+
for hs, hl in self._highlights:
|
|
574
|
+
he = hs + hl
|
|
575
|
+
if he <= start or hs >= end:
|
|
576
|
+
result.append((hs, hl))
|
|
577
|
+
continue
|
|
578
|
+
if hs < start:
|
|
579
|
+
result.append((hs, start - hs))
|
|
580
|
+
if he > end:
|
|
581
|
+
result.append((end, he - end))
|
|
582
|
+
self._highlights = self._merge_ranges(self._sanitize_ranges(result))
|
|
583
|
+
self.schedule_save()
|
|
584
|
+
|
|
585
|
+
def _selection_overlaps_any_highlight(self, s, e):
|
|
586
|
+
"""Check if selection overlaps any highlight"""
|
|
587
|
+
for hs, hl in self._highlights:
|
|
588
|
+
he = hs + hl
|
|
589
|
+
if not (he <= s or hs >= e):
|
|
590
|
+
return True
|
|
591
|
+
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pygpt-net
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.10
|
|
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, MCP, 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
|
|
@@ -120,7 +120,7 @@ Description-Content-Type: text/markdown
|
|
|
120
120
|
|
|
121
121
|
[](https://snapcraft.io/pygpt)
|
|
122
122
|
|
|
123
|
-
Release: **2.7.
|
|
123
|
+
Release: **2.7.10** | build: **2026-02-03** | Python: **>=3.10, <3.14**
|
|
124
124
|
|
|
125
125
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
|
126
126
|
>
|
|
@@ -3796,6 +3796,20 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3796
3796
|
|
|
3797
3797
|
## Recent changes:
|
|
3798
3798
|
|
|
3799
|
+
**2.7.10 (2026-02-03)**
|
|
3800
|
+
|
|
3801
|
+
- Fixed an issue where an avatar could be overwritten when creating a new preset.
|
|
3802
|
+
- Fixed an issue where a new context was not created when opening a new tab in the second column.
|
|
3803
|
+
- Added prompt history navigation to the input field (Ctrl + Up/Down arrow keys).
|
|
3804
|
+
- Added initial image centering when loading the Image Viewer.
|
|
3805
|
+
- Added a Mark/Unmark feature to the Notepad widget.
|
|
3806
|
+
- Added 18 new languages: Arabic (ar), Bulgarian (bg), Czech (cs), Danish (da), Finnish (fi), Hebrew (he), Hindi (hi), Hungarian (hu), Japanese (ja), Korean (ko), Dutch (nl), Norwegian (no), Portuguese (pt), Romanian (ro), Russian (ru), Slovak (sk), Swedish (sv), Turkish (tr).
|
|
3807
|
+
|
|
3808
|
+
**2.7.9 (2026-01-08)**
|
|
3809
|
+
|
|
3810
|
+
- Improved realtime audio mode.
|
|
3811
|
+
- Added xAI provider and Grok support in realtime audio mode.
|
|
3812
|
+
|
|
3799
3813
|
**2.7.8 (2026-01-06)**
|
|
3800
3814
|
|
|
3801
3815
|
- Added the xAI Collections remote tool and integrated collections management into the Remote Vector Stores tool.
|
|
@@ -3817,68 +3831,6 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3817
3831
|
- Added a zoom menu to textarea and web widgets.
|
|
3818
3832
|
- Added the ability to close tabs with a middle mouse button click.
|
|
3819
3833
|
|
|
3820
|
-
**2.7.5 (2026-01-03)**
|
|
3821
|
-
|
|
3822
|
-
- Added Sandbox/Playwright option to Computer Use mode.
|
|
3823
|
-
- Added support for Google models in Computer Use mode and introduced a new model: gemini-2.5-computer-use-preview-10-2025.
|
|
3824
|
-
- Added support for Google models in Research mode and introduced a new model: deep-research-pro-preview-12-2025.
|
|
3825
|
-
- Added the Google Vector Stores tool.
|
|
3826
|
-
|
|
3827
|
-
**2.7.4 (2025-12-31)**
|
|
3828
|
-
|
|
3829
|
-
- Added a splash screen.
|
|
3830
|
-
- Added Preview and Download links to the Image and Video outputs.
|
|
3831
|
-
- Added Negative prompt input to Image and Video mode.
|
|
3832
|
-
- Improved focus handling.
|
|
3833
|
-
- UI improvements.
|
|
3834
|
-
|
|
3835
|
-
**2.7.3 (2025-12-30)**
|
|
3836
|
-
|
|
3837
|
-
- Added the `Remix/Extend` option in Image and Video generation mode. This allows the use of a previously generated image or video as a reference. It can be used for adding or changing elements in a previously generated image or video instead of creating a new one from scratch. See the docs: `Modes -> Image and Video generation -> Remix, Edit, or Extend`.
|
|
3838
|
-
|
|
3839
|
-
**2.7.2 (2025-12-29)**
|
|
3840
|
-
|
|
3841
|
-
- Fixed: non-searchable combobox width.
|
|
3842
|
-
- Improved updater.
|
|
3843
|
-
- Added .AppImage build.
|
|
3844
|
-
|
|
3845
|
-
**2.7.1 (2025-12-28)**
|
|
3846
|
-
|
|
3847
|
-
- Improved UI elements.
|
|
3848
|
-
- Optimized Painter rendering and redraw functions.
|
|
3849
|
-
- Added Pack/Unpack feature to File Explorer.
|
|
3850
|
-
- Fixed: image restoration in Painter.
|
|
3851
|
-
- Fixed: tab title updating upon context deletion.
|
|
3852
|
-
|
|
3853
|
-
**2.7.0 (2025-12-28)**
|
|
3854
|
-
|
|
3855
|
-
- Added multi-select functionality using CTRL or SHIFT and batch actions to the context list, preset list, attachments list, and other list-based widgets.
|
|
3856
|
-
- Added a search field to comboboxes, such as the model selector.
|
|
3857
|
-
- Added a Duplicate option to the models editor.
|
|
3858
|
-
- Added drag-and-drop to context list.
|
|
3859
|
-
- Added multi-select, drag-and-drop, Cut, Copy, and Paste features to the File Explorer.
|
|
3860
|
-
- Fix: scroll restoration after actions in the context list.
|
|
3861
|
-
- Fix: 'Use as image' option in the File Explorer.
|
|
3862
|
-
- Fix: current preset system prompt disappearing on profile change.
|
|
3863
|
-
- Other UI fixes/improvements.
|
|
3864
|
-
|
|
3865
|
-
**2.6.67 (2025-12-26)**
|
|
3866
|
-
|
|
3867
|
-
- Added a provider filter to the models editor.
|
|
3868
|
-
- Added video options (resolution, duration) to the toolbox.
|
|
3869
|
-
- Updated the models configuration.
|
|
3870
|
-
|
|
3871
|
-
**2.6.66 (2025-12-25)**
|
|
3872
|
-
|
|
3873
|
-
- Added Sora 2 support - #155.
|
|
3874
|
-
- Added Nano Banana support.
|
|
3875
|
-
- Added Qdrant Vector Store - merged PR #147 by @Anush008.
|
|
3876
|
-
- Added models: gpt-5.2, gpt-image-1.5, gemini-3, nano-banana-pro, sora-2, claude-sonnet-4.5, claude-opus-4.5, veo-3.1.
|
|
3877
|
-
- Added Select/unselect All option in checkbox lists.
|
|
3878
|
-
- OpenAI SDK upgraded to 2.14.0, Anthropic SDK upgraded to 0.75.0, xAI SDK upgraded to 1.5.0, Google GenAI upgraded to 1.56.0, LlamaIndex upgraded to 0.14.10.
|
|
3879
|
-
- Fix: charset-normalizer 3.2.0 circular import - #152.
|
|
3880
|
-
- Fix: Google client closed state.
|
|
3881
|
-
|
|
3882
3834
|
# Credits and links
|
|
3883
3835
|
|
|
3884
3836
|
**Official website:** <https://pygpt.net>
|