pygpt-net 2.6.33__py3-none-any.whl → 2.6.36__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. pygpt_net/CHANGELOG.txt +18 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/assistant/batch.py +14 -4
  4. pygpt_net/controller/assistant/files.py +1 -0
  5. pygpt_net/controller/assistant/store.py +195 -1
  6. pygpt_net/controller/camera/camera.py +1 -1
  7. pygpt_net/controller/chat/common.py +58 -48
  8. pygpt_net/controller/chat/handler/stream_worker.py +55 -43
  9. pygpt_net/controller/config/placeholder.py +95 -75
  10. pygpt_net/controller/dialogs/confirm.py +3 -1
  11. pygpt_net/controller/media/media.py +11 -3
  12. pygpt_net/controller/painter/common.py +243 -13
  13. pygpt_net/controller/painter/painter.py +11 -2
  14. pygpt_net/core/assistants/files.py +18 -0
  15. pygpt_net/core/bridge/bridge.py +1 -5
  16. pygpt_net/core/bridge/context.py +81 -36
  17. pygpt_net/core/bridge/worker.py +3 -1
  18. pygpt_net/core/camera/camera.py +31 -402
  19. pygpt_net/core/camera/worker.py +430 -0
  20. pygpt_net/core/ctx/bag.py +4 -0
  21. pygpt_net/core/events/app.py +10 -17
  22. pygpt_net/core/events/base.py +17 -25
  23. pygpt_net/core/events/control.py +9 -17
  24. pygpt_net/core/events/event.py +9 -62
  25. pygpt_net/core/events/kernel.py +8 -17
  26. pygpt_net/core/events/realtime.py +8 -17
  27. pygpt_net/core/events/render.py +9 -17
  28. pygpt_net/core/filesystem/url.py +3 -0
  29. pygpt_net/core/render/web/body.py +483 -40
  30. pygpt_net/core/render/web/pid.py +39 -24
  31. pygpt_net/core/render/web/renderer.py +142 -36
  32. pygpt_net/core/text/utils.py +3 -0
  33. pygpt_net/data/config/config.json +4 -3
  34. pygpt_net/data/config/models.json +3 -3
  35. pygpt_net/data/config/settings.json +10 -5
  36. pygpt_net/data/css/web-blocks.css +4 -3
  37. pygpt_net/data/css/web-chatgpt.css +4 -2
  38. pygpt_net/data/css/web-chatgpt_wide.css +4 -2
  39. pygpt_net/data/locale/locale.de.ini +9 -7
  40. pygpt_net/data/locale/locale.en.ini +10 -6
  41. pygpt_net/data/locale/locale.es.ini +9 -7
  42. pygpt_net/data/locale/locale.fr.ini +9 -7
  43. pygpt_net/data/locale/locale.it.ini +9 -7
  44. pygpt_net/data/locale/locale.pl.ini +9 -7
  45. pygpt_net/data/locale/locale.uk.ini +9 -7
  46. pygpt_net/data/locale/locale.zh.ini +9 -7
  47. pygpt_net/item/assistant.py +13 -1
  48. pygpt_net/provider/api/google/__init__.py +46 -28
  49. pygpt_net/provider/api/openai/__init__.py +13 -10
  50. pygpt_net/provider/api/openai/store.py +45 -1
  51. pygpt_net/provider/core/config/patch.py +18 -0
  52. pygpt_net/provider/llms/google.py +4 -0
  53. pygpt_net/ui/dialog/assistant_store.py +213 -203
  54. pygpt_net/ui/layout/chat/input.py +3 -3
  55. pygpt_net/ui/layout/chat/painter.py +63 -4
  56. pygpt_net/ui/widget/draw/painter.py +715 -104
  57. pygpt_net/ui/widget/option/combo.py +5 -1
  58. pygpt_net/ui/widget/textarea/input.py +273 -3
  59. pygpt_net/ui/widget/textarea/web.py +2 -0
  60. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.36.dist-info}/METADATA +20 -2
  61. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.36.dist-info}/RECORD +64 -63
  62. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.36.dist-info}/LICENSE +0 -0
  63. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.36.dist-info}/WHEEL +0 -0
  64. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.36.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,19 @@
1
- # controller/painter/common.py
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: int
32
- :param height: int
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 = self.window.ui.nodes['painter.select.canvas.size'].currentData()
74
- if selected:
75
- size = self.convert_to_size(selected)
76
- # setCurrentText might not exist in the combo's items for custom sizes; harmless if it doesn't match
77
- self.window.ui.nodes['painter.select.canvas.size'].setCurrentText(selected)
78
- self.set_canvas_size(size[0], size[1])
79
- # resizing the widget triggers automatic image rescale in PainterWidget.resizeEvent
80
- self.window.core.config.set('painter.canvas.size', selected)
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
- self.window.ui.nodes['painter.select.brush.size'].findText(str(size))
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
- # controller/painter/painter.py
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.image.save(path)
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
@@ -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.08.30 06:00:00 #
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):
@@ -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.08.23 15:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
13
- from typing import Dict, Any
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 as e:
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()
@@ -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.08.30 06:00:00 #
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__()