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,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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.21 01:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Dict
|
|
@@ -41,7 +41,7 @@ class Mapping:
|
|
|
41
41
|
try:
|
|
42
42
|
if getter() == v:
|
|
43
43
|
continue
|
|
44
|
-
except Exception:
|
|
44
|
+
except Exception as e:
|
|
45
45
|
pass
|
|
46
46
|
setter(v)
|
|
47
47
|
except Exception:
|
|
@@ -91,6 +91,7 @@ class Mapping:
|
|
|
91
91
|
# output
|
|
92
92
|
nodes['output.timestamp'] = 'output.timestamp'
|
|
93
93
|
nodes['output.edit'] = 'output.edit'
|
|
94
|
+
nodes['output.raw'] = 'output.raw'
|
|
94
95
|
|
|
95
96
|
# painter
|
|
96
97
|
nodes['painter.btn.brush'] = 'painter.mode.paint'
|
|
@@ -199,6 +200,12 @@ class Mapping:
|
|
|
199
200
|
nodes['preset.tool.function.label.agent_llama'] = 'preset.tool.function.tip.agent_llama'
|
|
200
201
|
nodes['preset.btn.current'] = 'dialog.preset.btn.current'
|
|
201
202
|
nodes['preset.btn.save'] = 'dialog.preset.btn.save'
|
|
203
|
+
nodes['preset.editor.warn_label'] = 'preset.personalize.warning'
|
|
204
|
+
nodes['toolbox.model.label.label'] = 'toolbox.model.label'
|
|
205
|
+
nodes['preset.editor.personalize.avatar.choose'] = 'preset.personalize.avatar.choose'
|
|
206
|
+
nodes['preset.editor.personalize.avatar.remove'] = 'preset.personalize.avatar.remove'
|
|
207
|
+
nodes['preset.ai_avatar.label'] = 'preset.ai_avatar.label'
|
|
208
|
+
nodes['preset.ai_personalize.desc'] = 'preset.ai_personalize.desc'
|
|
202
209
|
|
|
203
210
|
# dialog: rename
|
|
204
211
|
nodes['dialog.rename.label'] = 'dialog.rename.title'
|
|
@@ -394,6 +401,11 @@ class Mapping:
|
|
|
394
401
|
menu_text['debug.db'] = 'menu.debug.db'
|
|
395
402
|
menu_text['debug.logger'] = 'menu.debug.logger'
|
|
396
403
|
menu_text['debug.app.log'] = 'menu.debug.app.log'
|
|
404
|
+
menu_text['debug.render'] = 'menu.debug.render'
|
|
405
|
+
menu_text['debug.indexes'] = 'menu.debug.indexes'
|
|
406
|
+
menu_text['debug.kernel'] = 'menu.debug.kernel'
|
|
407
|
+
menu_text['debug.tabs'] = 'menu.debug.tabs'
|
|
408
|
+
menu_text['debug.fixtures.stream'] = 'menu.debug.fixtures.stream'
|
|
397
409
|
|
|
398
410
|
dialog_title = {}
|
|
399
411
|
dialog_title['info.about'] = 'dialog.about.title'
|
|
@@ -434,6 +446,7 @@ class Mapping:
|
|
|
434
446
|
placeholders = {}
|
|
435
447
|
placeholders['ctx.search'] = 'ctx.list.search.placeholder'
|
|
436
448
|
placeholders['interpreter.input'] = 'interpreter.input.placeholder'
|
|
449
|
+
placeholders['input'] = 'input.placeholder'
|
|
437
450
|
|
|
438
451
|
mapping = {}
|
|
439
452
|
mapping['nodes'] = nodes
|
|
@@ -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: 2026.01.22 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional, Tuple
|
|
@@ -64,22 +64,24 @@ class Notepad:
|
|
|
64
64
|
self,
|
|
65
65
|
idx: Optional[int] = None,
|
|
66
66
|
tab: Optional[Tab] = None,
|
|
67
|
+
restore: bool = False
|
|
67
68
|
) -> Tuple[TabBody, int, int]:
|
|
68
69
|
"""
|
|
69
70
|
Create notepad widget
|
|
70
71
|
|
|
71
72
|
:param idx: notepad idx
|
|
72
73
|
:param tab: existing tab to use (optional)
|
|
74
|
+
:param restore: whether to restore existing notepad (default: False)
|
|
73
75
|
:return: notepad widget (TabBody)
|
|
74
76
|
"""
|
|
75
77
|
tabs_core = self.window.core.tabs
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
if not restore:
|
|
79
|
+
existing_tabs = tabs_core.get_tabs_by_type(Tab.TAB_NOTEPAD)
|
|
80
|
+
used_ids = {t.data_id for t in existing_tabs}
|
|
81
|
+
if idx is None:
|
|
82
|
+
idx = 1
|
|
83
|
+
while idx in used_ids:
|
|
84
|
+
idx += 1
|
|
83
85
|
|
|
84
86
|
suffix = self.get_next_suffix()
|
|
85
87
|
|
|
@@ -116,22 +118,33 @@ class Notepad:
|
|
|
116
118
|
self.update()
|
|
117
119
|
|
|
118
120
|
def load(self):
|
|
119
|
-
"""Load all notepads
|
|
120
|
-
self.window.core.notepad
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
"""Load all notepads"""
|
|
122
|
+
core_notepad = self.window.core.notepad
|
|
123
|
+
prev_locked = core_notepad.locked
|
|
124
|
+
core_notepad.locked = True # block any autosave during restore
|
|
125
|
+
try:
|
|
126
|
+
core_notepad.load_all()
|
|
127
|
+
items = core_notepad.get_all()
|
|
128
|
+
num_notepads = self.get_num_notepads()
|
|
129
|
+
|
|
130
|
+
if len(items) == 0:
|
|
131
|
+
if num_notepads > 0:
|
|
132
|
+
for idx in range(1, num_notepads + 1):
|
|
133
|
+
item = NotepadItem()
|
|
134
|
+
item.idx = idx
|
|
135
|
+
items[idx] = item
|
|
129
136
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
if num_notepads > 0:
|
|
138
|
+
for idx, item in items.items():
|
|
139
|
+
widget = self.window.ui.notepad.get(idx)
|
|
140
|
+
if widget is not None:
|
|
141
|
+
widget.setText(item.content)
|
|
142
|
+
highlights = item.highlights
|
|
143
|
+
widget.textarea.set_highlights(highlights)
|
|
144
|
+
if item.scroll_pos is not None and item.scroll_pos != -1:
|
|
145
|
+
widget.textarea.set_scroll_pos(item.scroll_pos)
|
|
146
|
+
finally:
|
|
147
|
+
core_notepad.locked = prev_locked # restore previous lock state
|
|
135
148
|
|
|
136
149
|
def get_notepad_name(self, idx: int):
|
|
137
150
|
"""
|
|
@@ -167,12 +180,36 @@ class Notepad:
|
|
|
167
180
|
|
|
168
181
|
widget = self.window.ui.notepad.get(idx)
|
|
169
182
|
if widget is not None:
|
|
183
|
+
if not widget.textarea.is_initialized():
|
|
184
|
+
return # do not save uninitialized notepads
|
|
170
185
|
text = widget.toPlainText()
|
|
171
186
|
if item.content != text:
|
|
172
187
|
item.content = text
|
|
188
|
+
self.save_state(idx, persist=False)
|
|
173
189
|
core_notepad.update(item)
|
|
190
|
+
else:
|
|
191
|
+
self.save_state(idx)
|
|
174
192
|
self.update()
|
|
175
193
|
|
|
194
|
+
def save_state(self, idx: int, persist: bool = True):
|
|
195
|
+
"""
|
|
196
|
+
Save highlights for given notepad idx into item.highlights
|
|
197
|
+
"""
|
|
198
|
+
widget = self.window.ui.notepad.get(idx)
|
|
199
|
+
if widget is None:
|
|
200
|
+
return
|
|
201
|
+
highlights = widget.textarea.get_highlights()
|
|
202
|
+
scroll_pos = widget.textarea.get_scroll_pos()
|
|
203
|
+
core_notepad = self.window.core.notepad
|
|
204
|
+
item = core_notepad.get_by_id(idx)
|
|
205
|
+
if item is None or core_notepad.locked:
|
|
206
|
+
return
|
|
207
|
+
item.highlights = highlights
|
|
208
|
+
if widget.textarea.is_initialized():
|
|
209
|
+
item.scroll_pos = scroll_pos
|
|
210
|
+
if persist:
|
|
211
|
+
core_notepad.update(item)
|
|
212
|
+
|
|
176
213
|
def save_all(self):
|
|
177
214
|
"""Save all notepads contents"""
|
|
178
215
|
items = self.window.core.notepad.get_all()
|
|
@@ -189,8 +226,9 @@ class Notepad:
|
|
|
189
226
|
text = widget.toPlainText()
|
|
190
227
|
if prev_content != text:
|
|
191
228
|
items[idx].content = text
|
|
229
|
+
self.save_state(idx, persist=False) # persist highlights for each notepad
|
|
192
230
|
self.window.core.notepad.update(items[idx])
|
|
193
|
-
|
|
231
|
+
self.update()
|
|
194
232
|
|
|
195
233
|
def setup(self):
|
|
196
234
|
"""Setup all notepads"""
|
|
@@ -320,6 +358,7 @@ class Notepad:
|
|
|
320
358
|
widget = self.window.ui.notepad.get(idx)
|
|
321
359
|
if widget is not None:
|
|
322
360
|
widget.textarea.clear()
|
|
361
|
+
widget.textarea.clear_highlights()
|
|
323
362
|
self.save(idx)
|
|
324
363
|
return True
|
|
325
364
|
return False
|
|
@@ -343,8 +382,12 @@ class Notepad:
|
|
|
343
382
|
widget = self.window.ui.notepad.get(idx)
|
|
344
383
|
if widget is None:
|
|
345
384
|
return
|
|
385
|
+
# only autoscroll brand-new notepad once, if there is no saved scroll position
|
|
346
386
|
if idx not in self.opened_idx:
|
|
347
|
-
|
|
387
|
+
item = self.window.core.notepad.get_by_id(idx)
|
|
388
|
+
has_saved_pos = item is not None and item.scroll_pos not in (None, -1)
|
|
389
|
+
if not has_saved_pos:
|
|
390
|
+
QTimer.singleShot(0, widget.scroll_to_bottom)
|
|
348
391
|
if not widget.opened:
|
|
349
392
|
widget.opened = True
|
|
350
393
|
if idx not in self.opened_idx:
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2026.01.20 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
13
13
|
import os
|
|
14
14
|
import shutil
|
|
15
|
+
import uuid
|
|
15
16
|
from typing import Any, Optional, Dict
|
|
16
17
|
|
|
17
18
|
from PySide6.QtCore import Slot, Qt
|
|
@@ -922,6 +923,7 @@ class Editor:
|
|
|
922
923
|
self.reload_all_custom_agent_options()
|
|
923
924
|
if self.opened:
|
|
924
925
|
self.init(self.current_id)
|
|
926
|
+
|
|
925
927
|
def init(self, id: Optional[str] = None):
|
|
926
928
|
"""
|
|
927
929
|
Initialize preset editor
|
|
@@ -935,8 +937,10 @@ class Editor:
|
|
|
935
937
|
data = PresetItem()
|
|
936
938
|
data.name = ""
|
|
937
939
|
data.filename = ""
|
|
940
|
+
data.uuid = str(uuid.uuid4())
|
|
938
941
|
|
|
939
942
|
if id is None:
|
|
943
|
+
self.current = None # RESET HERE is always required for new/avatar update
|
|
940
944
|
self.experts.update_list()
|
|
941
945
|
self.window.ui.config[self.id]['idx'].set_value("_") # reset idx combo if new preset
|
|
942
946
|
|
|
@@ -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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.20 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import re
|
|
@@ -24,14 +24,12 @@ from pygpt_net.core.types import (
|
|
|
24
24
|
MODE_AGENT_OPENAI,
|
|
25
25
|
)
|
|
26
26
|
from pygpt_net.controller.presets.editor import Editor
|
|
27
|
-
# Editor controller
|
|
28
27
|
from pygpt_net.core.events import AppEvent
|
|
29
28
|
from pygpt_net.item.preset import PresetItem
|
|
30
29
|
from pygpt_net.utils import trans
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
_FILENAME_SANITIZE_RE = re.compile(r'[^a-zA-Z0-9_\-\.]')
|
|
34
|
-
# keep original validation (do not break other parts)
|
|
35
33
|
_VALIDATE_FILENAME_RE = re.compile(r'[^\w\s\-\.]')
|
|
36
34
|
|
|
37
35
|
|
|
@@ -630,7 +628,12 @@ class Presets:
|
|
|
630
628
|
w.core.assistants.load()
|
|
631
629
|
w.core.remote_store.openai.load_all()
|
|
632
630
|
|
|
633
|
-
def _nearest_id_after_delete(
|
|
631
|
+
def _nearest_id_after_delete(
|
|
632
|
+
self,
|
|
633
|
+
mode: str,
|
|
634
|
+
idx: Optional[int],
|
|
635
|
+
deleting_id: Optional[str]
|
|
636
|
+
) -> Optional[str]:
|
|
634
637
|
"""
|
|
635
638
|
Compute the nearest neighbor to select after deletion:
|
|
636
639
|
- Prefer the next item (below) if exists;
|
|
@@ -847,11 +850,18 @@ class Presets:
|
|
|
847
850
|
# Drag & drop ordering helpers
|
|
848
851
|
# ----------------------------
|
|
849
852
|
|
|
850
|
-
def persist_order_for_mode(
|
|
853
|
+
def persist_order_for_mode(
|
|
854
|
+
self,
|
|
855
|
+
mode: str,
|
|
856
|
+
uuids: List[str]
|
|
857
|
+
):
|
|
851
858
|
"""
|
|
852
859
|
Persist new order (by UUIDs) for given mode.
|
|
853
860
|
|
|
854
861
|
The special '*' preset (current.<mode>) is not included here and always pinned at index 0.
|
|
862
|
+
|
|
863
|
+
:param mode: mode name
|
|
864
|
+
:param uuids: list of preset UUIDs in new order
|
|
855
865
|
"""
|
|
856
866
|
w = self.window
|
|
857
867
|
cfg = w.core.config
|
|
@@ -866,6 +876,8 @@ class Presets:
|
|
|
866
876
|
def dnd_enabled(self) -> bool:
|
|
867
877
|
"""
|
|
868
878
|
Check if drag and drop is globally enabled in config.
|
|
879
|
+
|
|
880
|
+
:return: True if enabled
|
|
869
881
|
"""
|
|
870
882
|
try:
|
|
871
883
|
return bool(self.window.core.config.get('presets.drag_and_drop.enabled'))
|
|
@@ -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: 2026.01.07 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Slot, QTimer
|
|
@@ -87,6 +87,8 @@ class Realtime:
|
|
|
87
87
|
self.window.core.api.google.realtime.handle_audio_input(event)
|
|
88
88
|
elif self.current_active == "openai":
|
|
89
89
|
self.window.core.api.openai.realtime.handle_audio_input(event)
|
|
90
|
+
elif self.current_active == "x_ai":
|
|
91
|
+
self.window.core.api.xai.realtime.handle_audio_input(event)
|
|
90
92
|
|
|
91
93
|
# begin: first text chunk or audio chunk received, start rendering
|
|
92
94
|
elif event.name == RealtimeEvent.RT_OUTPUT_READY:
|
|
@@ -216,6 +218,8 @@ class Realtime:
|
|
|
216
218
|
self.window.core.api.google.realtime.manual_commit()
|
|
217
219
|
elif self.current_active == "openai":
|
|
218
220
|
self.window.core.api.openai.realtime.manual_commit()
|
|
221
|
+
elif self.current_active == "x_ai":
|
|
222
|
+
self.window.core.api.xai.realtime.manual_commit()
|
|
219
223
|
|
|
220
224
|
def end_turn(self, ctx):
|
|
221
225
|
"""
|
|
@@ -252,6 +256,10 @@ class Realtime:
|
|
|
252
256
|
self.window.core.api.google.realtime.shutdown()
|
|
253
257
|
except Exception as e:
|
|
254
258
|
self.window.core.debug.log(f"[google] Realtime shutdown error: {e}")
|
|
259
|
+
try:
|
|
260
|
+
self.window.core.api.xai.realtime.shutdown()
|
|
261
|
+
except Exception as e:
|
|
262
|
+
self.window.core.debug.log(f"[xAI] Realtime shutdown error: {e}")
|
|
255
263
|
try:
|
|
256
264
|
self.manager.shutdown()
|
|
257
265
|
except Exception as e:
|
|
@@ -267,6 +275,10 @@ class Realtime:
|
|
|
267
275
|
self.window.core.api.google.realtime.reset()
|
|
268
276
|
except Exception as e:
|
|
269
277
|
self.window.core.debug.log(f"[google] Realtime reset error: {e}")
|
|
278
|
+
try:
|
|
279
|
+
self.window.core.api.xai.realtime.reset()
|
|
280
|
+
except Exception as e:
|
|
281
|
+
self.window.core.debug.log(f"[xAI] Realtime reset error: {e}")
|
|
270
282
|
|
|
271
283
|
def is_supported(self) -> bool:
|
|
272
284
|
"""
|
|
@@ -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: 2026.01.21 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -290,4 +290,13 @@ class Theme:
|
|
|
290
290
|
if self.current_theme != self.window.core.config.get('theme'):
|
|
291
291
|
self.setup()
|
|
292
292
|
self.update_style()
|
|
293
|
-
self.update_syntax()
|
|
293
|
+
self.update_syntax()
|
|
294
|
+
|
|
295
|
+
def is_dark_theme(self) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Check if current theme is dark
|
|
298
|
+
|
|
299
|
+
:return: True if dark theme, False otherwise
|
|
300
|
+
"""
|
|
301
|
+
current = self.window.core.config.get('theme')
|
|
302
|
+
return current.startswith('dark')
|
pygpt_net/controller/ui/tabs.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:
|
|
9
|
+
# Updated Date: 2026.01.22 04:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Any, Optional, Tuple
|
pygpt_net/core/ctx/output.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:
|
|
9
|
+
# Updated Date: 2026.01.22 04:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional, List, Dict
|
|
@@ -171,8 +171,11 @@ class Output:
|
|
|
171
171
|
tabs = self.window.core.tabs
|
|
172
172
|
active_pid = tabs.get_active_pid()
|
|
173
173
|
tab = tabs.get_tab_by_pid(active_pid)
|
|
174
|
+
in_second_column = False
|
|
174
175
|
if tab is None:
|
|
175
176
|
return None
|
|
177
|
+
|
|
178
|
+
# 1) check current column
|
|
176
179
|
col_idx = tab.column_idx
|
|
177
180
|
col_map = self.mapping.get(col_idx, {})
|
|
178
181
|
if not isinstance(col_map, dict):
|
|
@@ -182,7 +185,25 @@ class Output:
|
|
|
182
185
|
for pid in candidates:
|
|
183
186
|
if pid == active_pid: # prefer active PID
|
|
184
187
|
return pid
|
|
185
|
-
|
|
188
|
+
pid = candidates[-1] if candidates else None
|
|
189
|
+
|
|
190
|
+
# 2) check if mapped in other columns
|
|
191
|
+
if pid is None:
|
|
192
|
+
for idx, other_col_map in self.mapping.items():
|
|
193
|
+
if idx == col_idx:
|
|
194
|
+
continue
|
|
195
|
+
for other_pid, meta_id in other_col_map.items():
|
|
196
|
+
if meta_id == meta.id:
|
|
197
|
+
in_second_column = True
|
|
198
|
+
pid = other_pid
|
|
199
|
+
break
|
|
200
|
+
if pid is not None:
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
if in_second_column and not self.window.controller.chat.input.generating:
|
|
204
|
+
return None # allow remapping only when not generating
|
|
205
|
+
|
|
206
|
+
return pid # return last found PID
|
|
186
207
|
|
|
187
208
|
def is_empty(self) -> bool:
|
|
188
209
|
"""
|
|
@@ -214,15 +235,13 @@ class Output:
|
|
|
214
235
|
active_pid = tabs.get_active_pid()
|
|
215
236
|
mapped_pid = self.get_mapped(meta)
|
|
216
237
|
if mapped_pid == active_pid:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
self.last_pid = 0
|
|
225
|
-
self.initialized = False
|
|
238
|
+
pid = active_pid
|
|
239
|
+
else:
|
|
240
|
+
if mapped_pid is None:
|
|
241
|
+
pid = self.store(meta)
|
|
242
|
+
else:
|
|
243
|
+
pid = mapped_pid
|
|
244
|
+
return pid
|
|
226
245
|
|
|
227
246
|
def get_current(self, meta: Optional[CtxMeta] = None):
|
|
228
247
|
"""
|
|
@@ -313,4 +332,11 @@ class Output:
|
|
|
313
332
|
if pid in self.last_pids:
|
|
314
333
|
del self.last_pids[pid]
|
|
315
334
|
if pid == self.last_pid:
|
|
316
|
-
self.last_pid = 0
|
|
335
|
+
self.last_pid = 0
|
|
336
|
+
|
|
337
|
+
def clear(self):
|
|
338
|
+
"""Clear mapping"""
|
|
339
|
+
self.mapping.clear()
|
|
340
|
+
self.last_pids.clear()
|
|
341
|
+
self.last_pid = 0
|
|
342
|
+
self.initialized = False
|
pygpt_net/core/db/database.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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.22 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -162,6 +162,8 @@ class Database:
|
|
|
162
162
|
'idx',
|
|
163
163
|
'title',
|
|
164
164
|
'content',
|
|
165
|
+
'highlights_json',
|
|
166
|
+
'scroll_pos',
|
|
165
167
|
'created_ts',
|
|
166
168
|
'updated_ts',
|
|
167
169
|
'is_deleted',
|
|
@@ -287,7 +289,7 @@ class Database:
|
|
|
287
289
|
'sort_by': columns["notepad"],
|
|
288
290
|
'search_fields': ['id', 'title', 'content'],
|
|
289
291
|
'timestamp_columns': ['created_ts', 'updated_ts'],
|
|
290
|
-
'json_columns': [],
|
|
292
|
+
'json_columns': ['highlights_json'],
|
|
291
293
|
'default_sort': 'id',
|
|
292
294
|
'default_order': 'DESC',
|
|
293
295
|
'primary_key': 'id',
|
|
@@ -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: 2026.01.20 21:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtWidgets import QApplication
|
|
@@ -22,11 +22,39 @@ class Console:
|
|
|
22
22
|
:param window: Window instance
|
|
23
23
|
"""
|
|
24
24
|
self.window = window
|
|
25
|
+
# list of supported commands for auto-completion in ConsoleInput
|
|
26
|
+
self._supported_commands = [
|
|
27
|
+
"clr",
|
|
28
|
+
"mem",
|
|
29
|
+
"free",
|
|
30
|
+
"css",
|
|
31
|
+
"lang",
|
|
32
|
+
"oclr",
|
|
33
|
+
"dump(",
|
|
34
|
+
"js(",
|
|
35
|
+
"help",
|
|
36
|
+
"/help",
|
|
37
|
+
"/h",
|
|
38
|
+
"quit",
|
|
39
|
+
"exit",
|
|
40
|
+
"/q",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def get_supported_commands(self):
|
|
44
|
+
"""
|
|
45
|
+
Return list of supported commands for auto-completion.
|
|
46
|
+
|
|
47
|
+
:return: list of strings
|
|
48
|
+
"""
|
|
49
|
+
return list(self._supported_commands)
|
|
25
50
|
|
|
26
51
|
def on_send(self):
|
|
27
52
|
"""Called on send message from console"""
|
|
28
53
|
msg = self.window.console.text().strip()
|
|
29
54
|
if msg:
|
|
55
|
+
# push to history first to make it available for Up/Down
|
|
56
|
+
if hasattr(self.window.console, "add_to_history"):
|
|
57
|
+
self.window.console.add_to_history(msg)
|
|
30
58
|
self.clear()
|
|
31
59
|
self.log(msg)
|
|
32
60
|
QApplication.processEvents()
|
|
@@ -121,4 +149,4 @@ class Console:
|
|
|
121
149
|
" help - show this help message\n"
|
|
122
150
|
" quit|exit - close application\n"
|
|
123
151
|
" JS debug (window.*): STREAM_DEBUG|MD_LANG_DEBUG|CM_DEBUG\n"
|
|
124
|
-
)
|
|
152
|
+
)
|
pygpt_net/core/debug/context.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:
|
|
9
|
+
# Updated Date: 2026.01.22 04:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -47,6 +47,7 @@ class ContextDebug:
|
|
|
47
47
|
debug.add(self.id, 'current (id)', str(ctx_core.get_current()))
|
|
48
48
|
debug.add(self.id, 'len(meta)', len(ctx_core.meta))
|
|
49
49
|
debug.add(self.id, 'len(items)', len(ctx_core.get_items()))
|
|
50
|
+
debug.add(self.id, 'Stream PIDs', str(controller.chat.stream.get_pid_ids()))
|
|
50
51
|
debug.add(self.id, 'SYS PROMPT (current)', str(ctx_core.current_sys_prompt))
|
|
51
52
|
debug.add(self.id, 'CMD (current)', str(ctx_core.current_cmd))
|
|
52
53
|
debug.add(self.id, 'CMD schema (current)', str(ctx_core.current_cmd_schema))
|
pygpt_net/core/debug/ui.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:
|
|
9
|
+
# Updated Date: 2026.01.21 01:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
|
|
@@ -20,16 +20,38 @@ class UIDebug:
|
|
|
20
20
|
self.window = window
|
|
21
21
|
self.id = 'ui'
|
|
22
22
|
|
|
23
|
+
def _sortable_key(self, key):
|
|
24
|
+
"""
|
|
25
|
+
Return a safe, comparable key representation for sorting across heterogeneous key types.
|
|
26
|
+
Uses str() with robust fallbacks to avoid TypeError.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
return str(key)
|
|
30
|
+
except Exception:
|
|
31
|
+
try:
|
|
32
|
+
return repr(key)
|
|
33
|
+
except Exception:
|
|
34
|
+
return f"{type(key).__name__}@{id(key)}"
|
|
35
|
+
|
|
36
|
+
def _sorted_items(self, d: dict):
|
|
37
|
+
"""
|
|
38
|
+
Return dict items sorted by key at this nesting level.
|
|
39
|
+
"""
|
|
40
|
+
return sorted(d.items(), key=lambda kv: self._sortable_key(kv[0]))
|
|
41
|
+
|
|
23
42
|
def map_structure(self, d, show_value: bool = False):
|
|
24
43
|
"""
|
|
25
44
|
Map dict structure
|
|
26
45
|
|
|
27
46
|
:param d: data to map
|
|
28
47
|
:param show_value: show value
|
|
29
|
-
:return: mapped structure
|
|
48
|
+
:return: mapped structure with keys sorted at every nesting level
|
|
30
49
|
"""
|
|
31
50
|
if isinstance(d, dict):
|
|
32
|
-
|
|
51
|
+
out = {}
|
|
52
|
+
for k, v in self._sorted_items(d):
|
|
53
|
+
out[k] = self.map_structure(v, show_value)
|
|
54
|
+
return out
|
|
33
55
|
elif isinstance(d, list):
|
|
34
56
|
return [self.map_structure(item, show_value) for item in d]
|
|
35
57
|
else:
|
|
@@ -74,4 +96,4 @@ class UIDebug:
|
|
|
74
96
|
self.update_section(ui.splitters, 'splitters')
|
|
75
97
|
self.update_section(ui.tabs, 'tabs')
|
|
76
98
|
|
|
77
|
-
debug.end(self.id)
|
|
99
|
+
debug.end(self.id)
|
|
@@ -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: 2026.01.23 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -149,11 +149,12 @@ class Filesystem:
|
|
|
149
149
|
return str(os.path.join(*parts)) # rebuild OS directory separators
|
|
150
150
|
return path
|
|
151
151
|
|
|
152
|
-
def to_workdir(self, path: str) -> str:
|
|
152
|
+
def to_workdir(self, path: str, auto_prefix: bool = True) -> str:
|
|
153
153
|
"""
|
|
154
154
|
Replace user path with current workdir
|
|
155
155
|
|
|
156
156
|
:param path: path to fix
|
|
157
|
+
:param auto_prefix: add workdir prefix if missing
|
|
157
158
|
:return: path with replaced user workdir
|
|
158
159
|
"""
|
|
159
160
|
path = self.get_path(path)
|
|
@@ -166,6 +167,9 @@ class Filesystem:
|
|
|
166
167
|
work_dir
|
|
167
168
|
)
|
|
168
169
|
|
|
170
|
+
if not auto_prefix:
|
|
171
|
+
return path
|
|
172
|
+
|
|
169
173
|
# try to find workdir in path: old versions compatibility, < 2.0.113
|
|
170
174
|
if work_dir.endswith('.config/pygpt-net'):
|
|
171
175
|
work_dir = work_dir.rsplit('/.config/pygpt-net', 1)[0]
|