pygpt-net 2.6.33__py3-none-any.whl → 2.6.35__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/__init__.py +3 -3
- pygpt_net/controller/assistant/batch.py +14 -4
- pygpt_net/controller/assistant/files.py +1 -0
- pygpt_net/controller/assistant/store.py +195 -1
- pygpt_net/controller/camera/camera.py +1 -1
- pygpt_net/controller/chat/common.py +58 -48
- pygpt_net/controller/chat/handler/stream_worker.py +55 -43
- pygpt_net/controller/config/placeholder.py +95 -75
- pygpt_net/controller/dialogs/confirm.py +3 -1
- pygpt_net/controller/media/media.py +11 -3
- pygpt_net/controller/painter/common.py +243 -13
- pygpt_net/controller/painter/painter.py +11 -2
- pygpt_net/core/assistants/files.py +18 -0
- pygpt_net/core/bridge/bridge.py +1 -5
- pygpt_net/core/bridge/context.py +81 -36
- pygpt_net/core/bridge/worker.py +3 -1
- pygpt_net/core/camera/camera.py +31 -402
- pygpt_net/core/camera/worker.py +430 -0
- pygpt_net/core/ctx/bag.py +4 -0
- pygpt_net/core/events/app.py +10 -17
- pygpt_net/core/events/base.py +17 -25
- pygpt_net/core/events/control.py +9 -17
- pygpt_net/core/events/event.py +9 -62
- pygpt_net/core/events/kernel.py +8 -17
- pygpt_net/core/events/realtime.py +8 -17
- pygpt_net/core/events/render.py +9 -17
- pygpt_net/core/filesystem/url.py +3 -0
- pygpt_net/core/render/web/body.py +454 -40
- pygpt_net/core/render/web/pid.py +39 -24
- pygpt_net/core/render/web/renderer.py +146 -40
- pygpt_net/core/text/utils.py +3 -0
- pygpt_net/data/config/config.json +4 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +10 -5
- pygpt_net/data/css/web-blocks.css +3 -2
- pygpt_net/data/css/web-chatgpt.css +3 -1
- pygpt_net/data/css/web-chatgpt_wide.css +3 -1
- pygpt_net/data/locale/locale.de.ini +9 -7
- pygpt_net/data/locale/locale.en.ini +10 -6
- pygpt_net/data/locale/locale.es.ini +9 -7
- pygpt_net/data/locale/locale.fr.ini +9 -7
- pygpt_net/data/locale/locale.it.ini +9 -7
- pygpt_net/data/locale/locale.pl.ini +9 -7
- pygpt_net/data/locale/locale.uk.ini +9 -7
- pygpt_net/data/locale/locale.zh.ini +9 -7
- pygpt_net/item/assistant.py +13 -1
- pygpt_net/provider/api/google/__init__.py +46 -28
- pygpt_net/provider/api/openai/__init__.py +13 -10
- pygpt_net/provider/api/openai/store.py +45 -1
- pygpt_net/provider/core/config/patch.py +9 -0
- pygpt_net/provider/llms/google.py +4 -0
- pygpt_net/ui/dialog/assistant_store.py +213 -203
- pygpt_net/ui/layout/chat/input.py +3 -3
- pygpt_net/ui/layout/chat/painter.py +63 -4
- pygpt_net/ui/widget/draw/painter.py +715 -104
- pygpt_net/ui/widget/option/combo.py +5 -1
- pygpt_net/ui/widget/textarea/input.py +273 -3
- pygpt_net/ui/widget/textarea/web.py +2 -0
- {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.35.dist-info}/METADATA +16 -2
- {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.35.dist-info}/RECORD +64 -63
- {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.35.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.35.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.35.dist-info}/entry_points.txt +0 -0
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.09.02 15:00:00 #
|
|
10
|
+
# ================================================== #
|
|
2
11
|
|
|
3
12
|
from typing import Tuple, Optional, Dict, List
|
|
4
13
|
|
|
5
14
|
from PySide6.QtCore import Qt, QSize
|
|
6
15
|
from PySide6.QtGui import QColor
|
|
16
|
+
from PySide6.QtWidgets import QComboBox
|
|
7
17
|
|
|
8
18
|
|
|
9
19
|
class Common:
|
|
@@ -14,6 +24,12 @@ class Common:
|
|
|
14
24
|
:param window: Window instance
|
|
15
25
|
"""
|
|
16
26
|
self.window = window
|
|
27
|
+
# Guard to prevent re-entrancy when programmatically changing the combo/size
|
|
28
|
+
self._changing_canvas_size = False
|
|
29
|
+
# Cached set for predefined canvas sizes
|
|
30
|
+
self._predef_canvas_sizes_set = None
|
|
31
|
+
# Sticky custom value derived from the current "source" image (kept at index 0 when present)
|
|
32
|
+
self._sticky_custom_value: Optional[str] = None
|
|
17
33
|
|
|
18
34
|
def convert_to_size(self, canvas_size: str) -> Tuple[int, int]:
|
|
19
35
|
"""
|
|
@@ -28,8 +44,8 @@ class Common:
|
|
|
28
44
|
"""
|
|
29
45
|
Set canvas size
|
|
30
46
|
|
|
31
|
-
:param width:
|
|
32
|
-
:param height:
|
|
47
|
+
:param width: Canvas width
|
|
48
|
+
:param height: Canvas height
|
|
33
49
|
"""
|
|
34
50
|
self.window.ui.painter.setFixedSize(QSize(width, height))
|
|
35
51
|
|
|
@@ -69,16 +85,64 @@ class Common:
|
|
|
69
85
|
|
|
70
86
|
:param selected: Selected size
|
|
71
87
|
"""
|
|
88
|
+
# Re-entrancy guard to avoid loops when we adjust the combo programmatically
|
|
89
|
+
if self._changing_canvas_size:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
combo: QComboBox = self.window.ui.nodes['painter.select.canvas.size']
|
|
93
|
+
painter = self.window.ui.painter
|
|
94
|
+
|
|
95
|
+
# Heuristic to detect manual UI change vs programmatic call
|
|
96
|
+
# - manual if: no arg, or int index (Qt int overload), or arg equals currentText/currentData
|
|
97
|
+
raw_arg = selected
|
|
98
|
+
current_text = combo.currentText()
|
|
99
|
+
current_data = combo.currentData()
|
|
100
|
+
current_data_str = current_data if isinstance(current_data, str) else None
|
|
101
|
+
is_manual = (
|
|
102
|
+
raw_arg is None
|
|
103
|
+
or isinstance(raw_arg, int)
|
|
104
|
+
or (isinstance(raw_arg, str) and (raw_arg == current_text or (current_data_str and raw_arg == current_data_str)))
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Resolve selection if not passed explicitly; fallback to currentText if userData is missing
|
|
72
108
|
if not selected:
|
|
73
|
-
selected =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
selected = current_data_str or current_text
|
|
110
|
+
|
|
111
|
+
# Normalize to "WxH" strictly; if invalid, do nothing
|
|
112
|
+
selected_norm = self._normalize_canvas_value(selected)
|
|
113
|
+
if not selected_norm:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# Save undo only for manual changes and only if size will change
|
|
117
|
+
will_change = selected_norm != f"{painter.width()}x{painter.height()}"
|
|
118
|
+
if is_manual and will_change:
|
|
119
|
+
painter.saveForUndo()
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
self._changing_canvas_size = True
|
|
123
|
+
|
|
124
|
+
predef = self._get_predef_canvas_set()
|
|
125
|
+
|
|
126
|
+
# Sticky custom update only for programmatic (source-driven) changes
|
|
127
|
+
programmatic = not is_manual
|
|
128
|
+
if programmatic:
|
|
129
|
+
if selected_norm in predef:
|
|
130
|
+
self._sticky_custom_value = None
|
|
131
|
+
else:
|
|
132
|
+
self._sticky_custom_value = selected_norm
|
|
133
|
+
|
|
134
|
+
# Ensure combo reflects single custom at index 0 (sticky respected), then select current value
|
|
135
|
+
self._sync_canvas_size_combo(combo, selected_norm, sticky_to_keep=self._sticky_custom_value)
|
|
136
|
+
|
|
137
|
+
# Apply canvas size; PainterWidget handles rescaling in resizeEvent
|
|
138
|
+
w, h = self.convert_to_size(selected_norm)
|
|
139
|
+
self.set_canvas_size(w, h)
|
|
140
|
+
|
|
141
|
+
# Persist normalized value
|
|
142
|
+
self.window.core.config.set('painter.canvas.size', selected_norm)
|
|
81
143
|
self.window.core.config.save()
|
|
144
|
+
finally:
|
|
145
|
+
self._changing_canvas_size = False
|
|
82
146
|
|
|
83
147
|
def change_brush_size(self, size: int):
|
|
84
148
|
"""
|
|
@@ -141,9 +205,21 @@ class Common:
|
|
|
141
205
|
if self.window.core.config.has('painter.brush.size'):
|
|
142
206
|
size = int(self.window.core.config.get('painter.brush.size', 3))
|
|
143
207
|
self.window.ui.nodes['painter.select.brush.size'].setCurrentIndex(
|
|
144
|
-
|
|
208
|
+
self.window.ui.nodes['painter.select.brush.size'].findText(str(size))
|
|
145
209
|
)
|
|
146
210
|
|
|
211
|
+
def restore_zoom(self):
|
|
212
|
+
"""Restore zoom from config"""
|
|
213
|
+
if self.window.core.config.has('painter.zoom'):
|
|
214
|
+
zoom = int(self.window.core.config.get('painter.zoom', 100))
|
|
215
|
+
self.window.ui.painter.set_zoom_percent(zoom)
|
|
216
|
+
|
|
217
|
+
def save_zoom(self):
|
|
218
|
+
"""Save zoom to config"""
|
|
219
|
+
zoom = self.window.ui.painter.get_zoom_percent()
|
|
220
|
+
self.window.core.config.set('painter.zoom', zoom)
|
|
221
|
+
self.window.core.config.save()
|
|
222
|
+
|
|
147
223
|
def get_colors(self) -> Dict[str, QColor]:
|
|
148
224
|
"""
|
|
149
225
|
Get colors dict
|
|
@@ -191,4 +267,158 @@ class Common:
|
|
|
191
267
|
|
|
192
268
|
:return: path to capture directory
|
|
193
269
|
"""
|
|
194
|
-
return self.window.core.config.get_user_dir('capture')
|
|
270
|
+
return self.window.core.config.get_user_dir('capture')
|
|
271
|
+
|
|
272
|
+
# ---------- Public sync helper (used by PainterWidget undo/redo) ----------
|
|
273
|
+
|
|
274
|
+
def sync_canvas_combo_from_widget(self):
|
|
275
|
+
"""
|
|
276
|
+
Sync the size combobox with current PainterWidget canvas size.
|
|
277
|
+
Also derive sticky custom from the current source image if it is custom.
|
|
278
|
+
This method does not change the canvas size (UI-only sync).
|
|
279
|
+
"""
|
|
280
|
+
if self._changing_canvas_size:
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
combo: QComboBox = self.window.ui.nodes['painter.select.canvas.size']
|
|
284
|
+
painter = self.window.ui.painter
|
|
285
|
+
|
|
286
|
+
canvas_value = f"{painter.width()}x{painter.height()}"
|
|
287
|
+
canvas_norm = self._normalize_canvas_value(canvas_value)
|
|
288
|
+
if not canvas_norm:
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
# Derive sticky from current source image (if custom)
|
|
292
|
+
predef = self._get_predef_canvas_set()
|
|
293
|
+
sticky = None
|
|
294
|
+
if painter.sourceImageOriginal is not None and not painter.sourceImageOriginal.isNull():
|
|
295
|
+
src_val = f"{painter.sourceImageOriginal.width()}x{painter.sourceImageOriginal.height()}"
|
|
296
|
+
src_val = self._normalize_canvas_value(src_val)
|
|
297
|
+
if src_val and src_val not in predef:
|
|
298
|
+
sticky = src_val
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
self._changing_canvas_size = True
|
|
302
|
+
self._sticky_custom_value = sticky
|
|
303
|
+
self._sync_canvas_size_combo(combo, canvas_norm, sticky_to_keep=sticky)
|
|
304
|
+
|
|
305
|
+
# Persist canvas size only (do not change sticky config-scope)
|
|
306
|
+
self.window.core.config.set('painter.canvas.size', canvas_norm)
|
|
307
|
+
self.window.core.config.save()
|
|
308
|
+
finally:
|
|
309
|
+
self._changing_canvas_size = False
|
|
310
|
+
|
|
311
|
+
# ---------- Internal helpers ----------
|
|
312
|
+
|
|
313
|
+
def _normalize_canvas_value(self, value: Optional[str]) -> Optional[str]:
|
|
314
|
+
"""
|
|
315
|
+
Normalize arbitrary canvas string to canonical 'WxH'. Returns None if invalid.
|
|
316
|
+
Accepts variants like ' 1024 x 768 ', '1024×768', etc.
|
|
317
|
+
|
|
318
|
+
:param value: input value
|
|
319
|
+
:return: normalized value or None
|
|
320
|
+
"""
|
|
321
|
+
if not value:
|
|
322
|
+
return None
|
|
323
|
+
s = str(value).strip().lower().replace(' ', '').replace('×', 'x')
|
|
324
|
+
if 'x' not in s:
|
|
325
|
+
return None
|
|
326
|
+
parts = s.split('x', 1)
|
|
327
|
+
try:
|
|
328
|
+
w = int(parts[0])
|
|
329
|
+
h = int(parts[1])
|
|
330
|
+
except Exception:
|
|
331
|
+
return None
|
|
332
|
+
if w <= 0 or h <= 0:
|
|
333
|
+
return None
|
|
334
|
+
return f"{w}x{h}"
|
|
335
|
+
|
|
336
|
+
def _get_predef_canvas_set(self) -> set:
|
|
337
|
+
"""
|
|
338
|
+
Return cached set of predefined sizes for O(1) lookups.
|
|
339
|
+
|
|
340
|
+
:return: set of predefined sizes
|
|
341
|
+
"""
|
|
342
|
+
if self._predef_canvas_sizes_set is None:
|
|
343
|
+
self._predef_canvas_sizes_set = set(self.get_canvas_sizes())
|
|
344
|
+
return self._predef_canvas_sizes_set
|
|
345
|
+
|
|
346
|
+
def _find_index_for_value(self, combo: QComboBox, value: str) -> int:
|
|
347
|
+
"""
|
|
348
|
+
Find index by userData first, then by text. Returns -1 if not found.
|
|
349
|
+
|
|
350
|
+
:param combo: QComboBox
|
|
351
|
+
:param value: value to find
|
|
352
|
+
:return: index or -1
|
|
353
|
+
"""
|
|
354
|
+
idx = combo.findData(value)
|
|
355
|
+
if idx == -1:
|
|
356
|
+
idx = combo.findText(value, Qt.MatchFixedString)
|
|
357
|
+
return idx
|
|
358
|
+
|
|
359
|
+
def _remove_extra_custom_items(self, combo: QComboBox, predef: set, keep_index: int = -1):
|
|
360
|
+
"""
|
|
361
|
+
Remove all non-predefined items except one at keep_index (if set).
|
|
362
|
+
|
|
363
|
+
:param combo: QComboBox
|
|
364
|
+
:param predef: set of predefined values
|
|
365
|
+
:param keep_index: index to keep even if custom, or -1 to remove all custom
|
|
366
|
+
"""
|
|
367
|
+
for i in range(combo.count() - 1, -1, -1):
|
|
368
|
+
if i == keep_index:
|
|
369
|
+
continue
|
|
370
|
+
txt = combo.itemText(i)
|
|
371
|
+
if txt not in predef:
|
|
372
|
+
combo.removeItem(i)
|
|
373
|
+
|
|
374
|
+
def _ensure_custom_index0(self, combo: QComboBox, custom_value: str, predef: set):
|
|
375
|
+
"""
|
|
376
|
+
Ensure exactly one custom item exists at index 0 with given value.
|
|
377
|
+
|
|
378
|
+
:param combo: QComboBox
|
|
379
|
+
:param custom_value: custom value to set at index 0
|
|
380
|
+
:param predef: set of predefined values
|
|
381
|
+
"""
|
|
382
|
+
if combo.count() > 0 and combo.itemText(0) not in predef:
|
|
383
|
+
if combo.itemText(0) != custom_value:
|
|
384
|
+
combo.setItemText(0, custom_value)
|
|
385
|
+
combo.setItemData(0, custom_value)
|
|
386
|
+
else:
|
|
387
|
+
combo.insertItem(0, custom_value, custom_value)
|
|
388
|
+
self._remove_extra_custom_items(combo, predef, keep_index=0)
|
|
389
|
+
|
|
390
|
+
def _sync_canvas_size_combo(self, combo: QComboBox, value: str, sticky_to_keep: Optional[str]):
|
|
391
|
+
"""
|
|
392
|
+
Enforce invariant and selection:
|
|
393
|
+
- If sticky_to_keep is a custom value -> keep it as single custom item at index 0.
|
|
394
|
+
- If sticky_to_keep is None -> remove all custom items.
|
|
395
|
+
- Select 'value' in the combo. If value is custom and sticky_to_keep differs or is None,
|
|
396
|
+
ensure index 0 matches 'value' and select it.
|
|
397
|
+
|
|
398
|
+
:param combo: QComboBox
|
|
399
|
+
:param value: current canvas size value to select
|
|
400
|
+
:param sticky_to_keep: sticky custom value to keep at index 0, or None
|
|
401
|
+
"""
|
|
402
|
+
predef = self._get_predef_canvas_set()
|
|
403
|
+
|
|
404
|
+
# Maintain sticky custom slot (index 0) if provided
|
|
405
|
+
if sticky_to_keep and sticky_to_keep not in predef:
|
|
406
|
+
self._ensure_custom_index0(combo, sticky_to_keep, predef)
|
|
407
|
+
else:
|
|
408
|
+
self._remove_extra_custom_items(combo, predef, keep_index=-1)
|
|
409
|
+
|
|
410
|
+
# Select the current canvas value
|
|
411
|
+
if value in predef:
|
|
412
|
+
idx = self._find_index_for_value(combo, value)
|
|
413
|
+
if idx != -1 and idx != combo.currentIndex():
|
|
414
|
+
combo.setCurrentIndex(idx)
|
|
415
|
+
elif idx == -1:
|
|
416
|
+
# Fallback: set text (should not normally happen if combo prepopulates predefined sizes)
|
|
417
|
+
combo.setCurrentText(value)
|
|
418
|
+
else:
|
|
419
|
+
# Current value is custom: ensure it exists at index 0 and select it
|
|
420
|
+
# If sticky differs or is None, overwrite/create the custom at index 0 to reflect true current value.
|
|
421
|
+
if not sticky_to_keep or sticky_to_keep != value:
|
|
422
|
+
self._ensure_custom_index0(combo, value, predef)
|
|
423
|
+
if combo.currentIndex() != 0:
|
|
424
|
+
combo.setCurrentIndex(0)
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.09.02 15:00:00 #
|
|
10
|
+
# ================================================== #
|
|
2
11
|
|
|
3
12
|
import os
|
|
4
13
|
|
|
@@ -51,7 +60,7 @@ class Painter:
|
|
|
51
60
|
if not hasattr(self.window.ui, 'painter'):
|
|
52
61
|
return
|
|
53
62
|
path = os.path.join(self.common.get_capture_dir(), '_current.png')
|
|
54
|
-
self.window.ui.painter.
|
|
63
|
+
self.window.ui.painter.save_base(path, include_drawing=True)
|
|
55
64
|
|
|
56
65
|
def save_all(self):
|
|
57
66
|
"""Save all (on exit)"""
|
|
@@ -257,6 +257,24 @@ class Files:
|
|
|
257
257
|
del self.items[file.record_id]
|
|
258
258
|
return True
|
|
259
259
|
|
|
260
|
+
def delete_by_file_id(self, file_id: str) -> bool:
|
|
261
|
+
"""
|
|
262
|
+
Delete file by file ID
|
|
263
|
+
|
|
264
|
+
:param file_id: file ID
|
|
265
|
+
:return: True if deleted
|
|
266
|
+
"""
|
|
267
|
+
res = self.provider.delete_by_file_id(file_id)
|
|
268
|
+
if res:
|
|
269
|
+
# remove from items
|
|
270
|
+
to_delete = []
|
|
271
|
+
for id in self.items:
|
|
272
|
+
if self.items[id].file_id == file_id:
|
|
273
|
+
to_delete.append(id)
|
|
274
|
+
for id in to_delete:
|
|
275
|
+
del self.items[id]
|
|
276
|
+
return res
|
|
277
|
+
|
|
260
278
|
def on_store_deleted(self, store_id: str):
|
|
261
279
|
"""
|
|
262
280
|
Clear deleted store from files
|
pygpt_net/core/bridge/bridge.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: 2025.
|
|
9
|
+
# Updated Date: 2025.09.04 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import time
|
|
@@ -130,7 +130,6 @@ class Bridge:
|
|
|
130
130
|
self.window.core.debug.debug(str(debug))
|
|
131
131
|
|
|
132
132
|
self.apply_rate_limit() # apply RPM limit
|
|
133
|
-
self.last_context = weakref.ref(context) # store last context for call (debug)
|
|
134
133
|
|
|
135
134
|
if extra is None:
|
|
136
135
|
extra = {}
|
|
@@ -202,9 +201,6 @@ class Bridge:
|
|
|
202
201
|
debug = {k: str(v) for k, v in context.to_dict().items()}
|
|
203
202
|
self.window.core.debug.debug(str(debug))
|
|
204
203
|
|
|
205
|
-
# --- DEBUG ONLY ---
|
|
206
|
-
self.last_context_quick = weakref.ref(context) # store last context for quick call (debug)
|
|
207
|
-
|
|
208
204
|
if context.model is not None:
|
|
209
205
|
# check if model is supported by OpenAI API, if not then try to use llama-index or langchain call
|
|
210
206
|
if not context.model.is_supported(MODE_CHAT):
|
pygpt_net/core/bridge/context.py
CHANGED
|
@@ -6,30 +6,101 @@
|
|
|
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.04 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
from
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Dict, Any, Optional
|
|
14
15
|
|
|
15
16
|
from pygpt_net.item.ctx import CtxItem
|
|
16
17
|
from pygpt_net.item.model import ModelItem
|
|
17
18
|
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class MultimodalContext:
|
|
22
|
+
"""
|
|
23
|
+
Multimodal context
|
|
24
|
+
"""
|
|
25
|
+
is_audio_input: bool = False
|
|
26
|
+
is_audio_output: bool = False
|
|
27
|
+
audio_data: Optional[Any] = None
|
|
28
|
+
audio_format: str = "wav"
|
|
29
|
+
|
|
30
|
+
def __init__(self, **kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Multimodal context
|
|
33
|
+
|
|
34
|
+
:param kwargs: keyword arguments
|
|
35
|
+
"""
|
|
36
|
+
# kwargs-based initialization
|
|
37
|
+
self.is_audio_input = kwargs.get("is_audio_input", False)
|
|
38
|
+
self.is_audio_output = kwargs.get("is_audio_output", False)
|
|
39
|
+
self.audio_data = kwargs.get("audio_data", None)
|
|
40
|
+
self.audio_format = kwargs.get("audio_format", "wav")
|
|
41
|
+
|
|
42
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Return as dictionary
|
|
45
|
+
|
|
46
|
+
:return: dictionary
|
|
47
|
+
"""
|
|
48
|
+
data = {
|
|
49
|
+
"is_audio_input": self.is_audio_input,
|
|
50
|
+
"is_audio_output": self.is_audio_output,
|
|
51
|
+
"audio_format": self.audio_format,
|
|
52
|
+
}
|
|
53
|
+
# sort by keys
|
|
54
|
+
return dict(sorted(data.items(), key=lambda item: item[0]))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(slots=True)
|
|
18
58
|
class BridgeContext:
|
|
59
|
+
"""
|
|
60
|
+
Bridge context
|
|
61
|
+
"""
|
|
62
|
+
assistant_id: str = ""
|
|
63
|
+
attachments: dict = field(default_factory=dict)
|
|
64
|
+
ctx: Optional[CtxItem] = None
|
|
65
|
+
external_functions: list = field(default_factory=list)
|
|
66
|
+
file_ids: list = field(default_factory=list)
|
|
67
|
+
force: bool = False # Force mode flag
|
|
68
|
+
force_sync: bool = False # Force sync flag
|
|
69
|
+
history: list = field(default_factory=list)
|
|
70
|
+
idx: Optional[Any] = None
|
|
71
|
+
idx_mode: str = "chat"
|
|
72
|
+
is_expert_call: bool = False # Expert call flag
|
|
73
|
+
max_tokens: int = 0
|
|
74
|
+
mode: Optional[Any] = None
|
|
75
|
+
model: Optional[ModelItem] = None # model instance, not model name
|
|
76
|
+
multimodal_ctx: MultimodalContext = field(default_factory=lambda: MultimodalContext()) # AudioContext
|
|
77
|
+
parent_mode: Optional[Any] = None # real mode (global)
|
|
78
|
+
preset: Optional[Any] = None # PresetItem
|
|
79
|
+
prompt: str = ""
|
|
80
|
+
reply_context: Optional[Any] = None # ReplyContext
|
|
81
|
+
request: bool = False # Use normal request instead of quick call
|
|
82
|
+
stream: bool = False
|
|
83
|
+
system_prompt: str = ""
|
|
84
|
+
system_prompt_raw: str = "" # without plugins addons
|
|
85
|
+
temperature: float = 1.0
|
|
86
|
+
thread_id: str = ""
|
|
87
|
+
tools_outputs: list = field(default_factory=list)
|
|
88
|
+
|
|
19
89
|
def __init__(self, **kwargs):
|
|
20
90
|
"""
|
|
21
91
|
Bridge context
|
|
22
92
|
|
|
23
93
|
:param kwargs: keyword arguments
|
|
24
94
|
"""
|
|
95
|
+
# Assign with defaults
|
|
25
96
|
self.assistant_id = kwargs.get("assistant_id", "")
|
|
26
|
-
self.attachments = kwargs.get("attachments", [])
|
|
97
|
+
self.attachments = dict(kwargs.get("attachments", []))
|
|
27
98
|
self.ctx = kwargs.get("ctx", None)
|
|
28
|
-
self.external_functions = kwargs.get("external_functions", [])
|
|
29
|
-
self.file_ids = kwargs.get("file_ids", [])
|
|
99
|
+
self.external_functions = list(kwargs.get("external_functions", []))
|
|
100
|
+
self.file_ids = list(kwargs.get("file_ids", []))
|
|
30
101
|
self.force = kwargs.get("force", False) # Force mode flag
|
|
31
102
|
self.force_sync = kwargs.get("force_sync", False) # Force sync flag
|
|
32
|
-
self.history = kwargs.get("history", [])
|
|
103
|
+
self.history = list(kwargs.get("history", []))
|
|
33
104
|
self.idx = kwargs.get("idx", None)
|
|
34
105
|
self.idx_mode = kwargs.get("idx_mode", "chat")
|
|
35
106
|
self.is_expert_call = kwargs.get("is_expert_call", False) # Expert call flag
|
|
@@ -40,14 +111,14 @@ class BridgeContext:
|
|
|
40
111
|
self.parent_mode = kwargs.get("parent_mode", None) # real mode (global)
|
|
41
112
|
self.preset = kwargs.get("preset", None) # PresetItem
|
|
42
113
|
self.prompt = kwargs.get("prompt", "")
|
|
43
|
-
self.reply_context = kwargs.get("reply_ctx", None) # ReplyContext
|
|
114
|
+
self.reply_context = kwargs.get("reply_ctx", kwargs.get("reply_context", None)) # ReplyContext
|
|
44
115
|
self.request = kwargs.get("request", False) # Use normal request instead of quick call
|
|
45
116
|
self.stream = kwargs.get("stream", False)
|
|
46
117
|
self.system_prompt = kwargs.get("system_prompt", "")
|
|
47
118
|
self.system_prompt_raw = kwargs.get("system_prompt_raw", "") # without plugins addons
|
|
48
119
|
self.temperature = kwargs.get("temperature", 1.0)
|
|
49
120
|
self.thread_id = kwargs.get("thread_id", "")
|
|
50
|
-
self.tools_outputs = kwargs.get("tools_outputs", [])
|
|
121
|
+
self.tools_outputs = list(kwargs.get("tools_outputs", []))
|
|
51
122
|
|
|
52
123
|
# check types
|
|
53
124
|
if self.ctx is not None and not isinstance(self.ctx, CtxItem):
|
|
@@ -101,36 +172,10 @@ class BridgeContext:
|
|
|
101
172
|
"""
|
|
102
173
|
try:
|
|
103
174
|
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
|
|
104
|
-
except Exception
|
|
175
|
+
except Exception:
|
|
105
176
|
pass
|
|
106
177
|
return ""
|
|
107
178
|
|
|
108
179
|
def __str__(self):
|
|
109
180
|
"""To string"""
|
|
110
|
-
return self.dump()
|
|
111
|
-
|
|
112
|
-
class MultimodalContext:
|
|
113
|
-
def __init__(self, **kwargs):
|
|
114
|
-
"""
|
|
115
|
-
Multimodal context
|
|
116
|
-
|
|
117
|
-
:param kwargs: keyword arguments
|
|
118
|
-
"""
|
|
119
|
-
self.is_audio_input = kwargs.get("is_audio_input", False)
|
|
120
|
-
self.is_audio_output = kwargs.get("is_audio_output", False)
|
|
121
|
-
self.audio_data = kwargs.get("audio_data", None)
|
|
122
|
-
self.audio_format = kwargs.get("audio_format", "wav")
|
|
123
|
-
|
|
124
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
125
|
-
"""
|
|
126
|
-
Return as dictionary
|
|
127
|
-
|
|
128
|
-
:return: dictionary
|
|
129
|
-
"""
|
|
130
|
-
data = {
|
|
131
|
-
"is_audio_input": self.is_audio_input,
|
|
132
|
-
"is_audio_output": self.is_audio_output,
|
|
133
|
-
"audio_format": self.audio_format,
|
|
134
|
-
}
|
|
135
|
-
# sort by keys
|
|
136
|
-
return dict(sorted(data.items(), key=lambda item: item[0]))
|
|
181
|
+
return self.dump()
|
pygpt_net/core/bridge/worker.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: 2025.
|
|
9
|
+
# Updated Date: 2025.09.04 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import QObject, Signal, QRunnable, Slot
|
|
@@ -29,6 +29,8 @@ class BridgeSignals(QObject):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class BridgeWorker(QRunnable):
|
|
32
|
+
__slots__ = ('signals', 'rt_signals', 'args', 'kwargs', 'window', 'context', 'extra', 'mode')
|
|
33
|
+
|
|
32
34
|
"""Bridge worker"""
|
|
33
35
|
def __init__(self, *args, **kwargs):
|
|
34
36
|
super().__init__()
|