pygpt-net 2.6.34__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.
Files changed (44) hide show
  1. pygpt_net/CHANGELOG.txt +7 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/chat/common.py +8 -2
  4. pygpt_net/controller/chat/handler/stream_worker.py +55 -43
  5. pygpt_net/controller/painter/common.py +13 -1
  6. pygpt_net/controller/painter/painter.py +11 -2
  7. pygpt_net/core/bridge/bridge.py +1 -5
  8. pygpt_net/core/bridge/context.py +81 -36
  9. pygpt_net/core/bridge/worker.py +3 -1
  10. pygpt_net/core/ctx/bag.py +4 -0
  11. pygpt_net/core/events/app.py +10 -17
  12. pygpt_net/core/events/base.py +17 -25
  13. pygpt_net/core/events/control.py +9 -17
  14. pygpt_net/core/events/event.py +9 -62
  15. pygpt_net/core/events/kernel.py +8 -17
  16. pygpt_net/core/events/realtime.py +8 -17
  17. pygpt_net/core/events/render.py +9 -17
  18. pygpt_net/core/render/web/body.py +394 -36
  19. pygpt_net/core/render/web/pid.py +39 -24
  20. pygpt_net/core/render/web/renderer.py +146 -40
  21. pygpt_net/data/config/config.json +4 -3
  22. pygpt_net/data/config/models.json +3 -3
  23. pygpt_net/data/css/web-blocks.css +3 -2
  24. pygpt_net/data/css/web-chatgpt.css +3 -1
  25. pygpt_net/data/css/web-chatgpt_wide.css +3 -1
  26. pygpt_net/data/locale/locale.de.ini +1 -0
  27. pygpt_net/data/locale/locale.en.ini +3 -2
  28. pygpt_net/data/locale/locale.es.ini +1 -0
  29. pygpt_net/data/locale/locale.fr.ini +1 -0
  30. pygpt_net/data/locale/locale.it.ini +1 -0
  31. pygpt_net/data/locale/locale.pl.ini +2 -1
  32. pygpt_net/data/locale/locale.uk.ini +1 -0
  33. pygpt_net/data/locale/locale.zh.ini +1 -0
  34. pygpt_net/provider/api/google/__init__.py +14 -5
  35. pygpt_net/provider/api/openai/__init__.py +13 -10
  36. pygpt_net/provider/core/config/patch.py +9 -0
  37. pygpt_net/ui/layout/chat/painter.py +63 -4
  38. pygpt_net/ui/widget/draw/painter.py +702 -106
  39. pygpt_net/ui/widget/textarea/web.py +2 -0
  40. {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/METADATA +9 -2
  41. {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/RECORD +44 -44
  42. {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/LICENSE +0 -0
  43. {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/WHEEL +0 -0
  44. {pygpt_net-2.6.34.dist-info → pygpt_net-2.6.35.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,10 @@
1
+ 2.6.35 (2025-09-04)
2
+
3
+ - Improved responsiveness in chat streaming.
4
+ - Added zoom, fit, and auto-scroll on crop in Painter.
5
+ - Added a scroll-up button in the chat window.
6
+ - Optimized memory usage.
7
+
1
8
  2.6.34 (2025-09-03)
2
9
 
3
10
  - Added auto-resizing for input.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.03 00:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.34"
17
- __build__ = "2025-09-03"
16
+ __version__ = "2.6.35"
17
+ __build__ = "2025-09-04"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
@@ -292,8 +292,6 @@ class Common:
292
292
  })) # stop audio input
293
293
  controller.kernel.halt = True
294
294
  dispatch(RenderEvent(RenderEvent.TOOL_END)) # show waiting
295
-
296
- core.api.openai.stop()
297
295
  self.unlock_input()
298
296
 
299
297
  controller.chat.input.generating = False
@@ -311,6 +309,14 @@ class Common:
311
309
  if not exit:
312
310
  dispatch(AppEvent(AppEvent.INPUT_STOPPED)) # app event
313
311
 
312
+ self.stop_client() # stop clients
313
+
314
+ def stop_client(self):
315
+ """Stop all clients"""
316
+ return # TODO: make it work without connection error after close
317
+ self.window.core.api.openai.safe_close()
318
+ self.window.core.api.google.safe_close()
319
+
314
320
  def check_api_key(
315
321
  self,
316
322
  mode: str,
@@ -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.28 20:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
@@ -14,6 +14,7 @@ import io
14
14
  import json
15
15
  from dataclasses import dataclass, field
16
16
  from typing import Optional, Literal, Any
17
+ from enum import Enum
17
18
 
18
19
  from PySide6.QtCore import QObject, Signal, Slot, QRunnable
19
20
 
@@ -40,16 +41,18 @@ EventType = Literal[
40
41
  "error",
41
42
  ]
42
43
 
43
- # Chunks
44
- ChunkType = Literal[
45
- "api_chat",
46
- "api_chat_responses",
47
- "api_completion",
48
- "langchain_chat",
49
- "llama_chat",
50
- "google",
51
- "raw",
52
- ]
44
+
45
+ class ChunkType(str, Enum):
46
+ """
47
+ Enum for chunk type classification.
48
+ """
49
+ API_CHAT = "api_chat"
50
+ API_CHAT_RESPONSES = "api_chat_responses"
51
+ API_COMPLETION = "api_completion"
52
+ LANGCHAIN_CHAT = "langchain_chat"
53
+ LLAMA_CHAT = "llama_chat"
54
+ GOOGLE = "google"
55
+ RAW = "raw"
53
56
 
54
57
 
55
58
  class WorkerSignals(QObject):
@@ -64,10 +67,10 @@ class WorkerSignals(QObject):
64
67
  eventReady = Signal(object)
65
68
 
66
69
 
67
- @dataclass
70
+ @dataclass(slots=True)
68
71
  class WorkerState:
69
72
  """Holds mutable state for the streaming loop."""
70
- output_parts: list[str] = field(default_factory=list)
73
+ out: Optional[io.StringIO] = None
71
74
  output_tokens: int = 0
72
75
  begin: bool = True
73
76
  error: Optional[Exception] = None
@@ -81,7 +84,7 @@ class WorkerState:
81
84
  is_code: bool = False
82
85
  force_func_call: bool = False
83
86
  stopped: bool = False
84
- chunk_type: ChunkType = "raw"
87
+ chunk_type: ChunkType = ChunkType.RAW
85
88
  generator: Any = None
86
89
  usage_vendor: Optional[str] = None
87
90
  usage_payload: dict = field(default_factory=dict)
@@ -90,6 +93,8 @@ class WorkerState:
90
93
 
91
94
 
92
95
  class StreamWorker(QRunnable):
96
+ __slots__ = ("signals", "ctx", "window", "stream")
97
+
93
98
  def __init__(self, ctx: CtxItem, window, parent=None):
94
99
  super().__init__()
95
100
  self.signals = WorkerSignals()
@@ -134,7 +139,7 @@ class StreamWorker(QRunnable):
134
139
  if ctx.use_responses_api:
135
140
  if hasattr(chunk, 'type'):
136
141
  etype = chunk.type # type: ignore[assignment]
137
- state.chunk_type = "api_chat_responses"
142
+ state.chunk_type = ChunkType.API_CHAT_RESPONSES
138
143
  else:
139
144
  continue
140
145
  else:
@@ -199,23 +204,21 @@ class StreamWorker(QRunnable):
199
204
  :param chunk: The chunk object from the stream
200
205
  :return: Detected ChunkType
201
206
  """
202
- if (hasattr(chunk, 'choices')
203
- and chunk.choices
204
- and hasattr(chunk.choices[0], 'delta')
205
- and chunk.choices[0].delta is not None):
206
- return "api_chat"
207
- if (hasattr(chunk, 'choices')
208
- and chunk.choices
209
- and hasattr(chunk.choices[0], 'text')
210
- and chunk.choices[0].text is not None):
211
- return "api_completion"
212
- if hasattr(chunk, 'content') and chunk.content is not None:
213
- return "langchain_chat"
214
- if hasattr(chunk, 'delta') and chunk.delta is not None:
215
- return "llama_chat"
216
- if hasattr(chunk, "candidates"): # Google python-genai chunk
217
- return "google"
218
- return "raw"
207
+ choices = getattr(chunk, 'choices', None)
208
+ if choices:
209
+ choice0 = choices[0] if len(choices) > 0 else None
210
+ if choice0 is not None and hasattr(choice0, 'delta') and choice0.delta is not None:
211
+ return ChunkType.API_CHAT
212
+ if choice0 is not None and hasattr(choice0, 'text') and choice0.text is not None:
213
+ return ChunkType.API_COMPLETION
214
+
215
+ if hasattr(chunk, 'content') and getattr(chunk, 'content') is not None:
216
+ return ChunkType.LANGCHAIN_CHAT
217
+ if hasattr(chunk, 'delta') and getattr(chunk, 'delta') is not None:
218
+ return ChunkType.LLAMA_CHAT
219
+ if hasattr(chunk, "candidates"):
220
+ return ChunkType.GOOGLE
221
+ return ChunkType.RAW
219
222
 
220
223
  def _append_response(
221
224
  self,
@@ -236,7 +239,10 @@ class StreamWorker(QRunnable):
236
239
  """
237
240
  if state.begin and response == "":
238
241
  return
239
- state.output_parts.append(response)
242
+ # Use a single expandable buffer to avoid per-chunk list allocations
243
+ if state.out is None:
244
+ state.out = io.StringIO()
245
+ state.out.write(response)
240
246
  state.output_tokens += 1
241
247
  emit_event(
242
248
  RenderEvent(
@@ -307,9 +313,14 @@ class StreamWorker(QRunnable):
307
313
  :param state: WorkerState
308
314
  :param emit_end: Function to emit end signal
309
315
  """
310
- # Build final output
311
- output = "".join(state.output_parts)
312
- state.output_parts.clear()
316
+ # Build final output from the incremental buffer
317
+ output = state.out.getvalue() if state.out is not None else ""
318
+ if state.out is not None:
319
+ try:
320
+ state.out.close()
321
+ except Exception:
322
+ pass
323
+ state.out = None
313
324
 
314
325
  if has_unclosed_code_tag(output):
315
326
  output += "\n```"
@@ -336,6 +347,7 @@ class StreamWorker(QRunnable):
336
347
 
337
348
  self.stream = None
338
349
  ctx.output = output
350
+ output = None # free ref
339
351
 
340
352
  # Tokens usage
341
353
  if state.usage_payload:
@@ -418,17 +430,17 @@ class StreamWorker(QRunnable):
418
430
  :return: Response delta string or None
419
431
  """
420
432
  t = state.chunk_type
421
- if t == "api_chat":
433
+ if t == ChunkType.API_CHAT:
422
434
  return self._process_api_chat(ctx, state, chunk)
423
- if t == "api_chat_responses":
435
+ if t == ChunkType.API_CHAT_RESPONSES:
424
436
  return self._process_api_chat_responses(ctx, core, state, chunk, etype)
425
- if t == "api_completion":
437
+ if t == ChunkType.API_COMPLETION:
426
438
  return self._process_api_completion(chunk)
427
- if t == "langchain_chat":
439
+ if t == ChunkType.LANGCHAIN_CHAT:
428
440
  return self._process_langchain_chat(chunk)
429
- if t == "llama_chat":
441
+ if t == ChunkType.LLAMA_CHAT:
430
442
  return self._process_llama_chat(state, chunk)
431
- if t == "google":
443
+ if t == ChunkType.GOOGLE:
432
444
  return self._process_google_chunk(ctx, core, state, chunk)
433
445
  # raw fallback
434
446
  return self._process_raw(chunk)
@@ -1109,7 +1121,7 @@ class StreamWorker(QRunnable):
1109
1121
  except Exception:
1110
1122
  pass
1111
1123
 
1112
- # Bind to ctx on first discovery for compatibility with other parts of the app
1124
+ # Bind to ctx on first discovery
1113
1125
  if state.citations and (ctx.urls is None or not ctx.urls):
1114
1126
  ctx.urls = list(state.citations)
1115
1127
 
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.02 20:00:00 #
9
+ # Updated Date: 2025.09.02 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Tuple, Optional, Dict, List
@@ -208,6 +208,18 @@ class Common:
208
208
  self.window.ui.nodes['painter.select.brush.size'].findText(str(size))
209
209
  )
210
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
+
211
223
  def get_colors(self) -> Dict[str, QColor]:
212
224
  """
213
225
  Get colors dict
@@ -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)"""
@@ -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__()
pygpt_net/core/ctx/bag.py CHANGED
@@ -15,9 +15,13 @@ from pygpt_net.item.ctx import CtxItem
15
15
 
16
16
 
17
17
  class Bag:
18
+ __slots__ = ('window', 'meta', 'tab_id', 'items')
19
+
18
20
  def __init__(self, window=None):
19
21
  """
20
22
  Context bag
23
+
24
+ :param window: Window instance
21
25
  """
22
26
  self.window = window
23
27
  self.meta = None # current meta
@@ -6,15 +6,22 @@
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 21:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from typing import Optional
12
+ from dataclasses import dataclass
13
+ from typing import Optional, Dict, Any, ClassVar
13
14
 
14
15
  from .base import BaseEvent
16
+ from ...item.ctx import CtxItem
15
17
 
18
+
19
+ @dataclass(slots=True)
16
20
  class AppEvent(BaseEvent):
17
21
  """Events dispatched by application"""
22
+ # static id for event family
23
+ id: ClassVar[str] = "AppEvent"
24
+
18
25
  APP_STARTED = "app.started"
19
26
  CAMERA_CAPTURED = "camera.captured"
20
27
  CAMERA_DISABLED = "camera.disabled"
@@ -37,18 +44,4 @@ class AppEvent(BaseEvent):
37
44
  VOICE_CONTROL_STOPPED = "voice.control.stopped"
38
45
  VOICE_CONTROL_SENT = "voice.control.sent"
39
46
  VOICE_CONTROL_TOGGLE = "voice.control.toggle"
40
- VOICE_CONTROL_UNRECOGNIZED = "voice.control.unrecognized"
41
-
42
- def __init__(
43
- self,
44
- name: Optional[str] = None,
45
- data: Optional[dict] = None,
46
- ):
47
- """
48
- Event object class
49
-
50
- :param name: event name
51
- :param data: event data
52
- """
53
- super(AppEvent, self).__init__(name, data)
54
- self.id = "AppEvent"
47
+ VOICE_CONTROL_UNRECOGNIZED = "voice.control.unrecognized"
@@ -6,39 +6,31 @@
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.06 19:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
13
- from typing import Optional, Dict, Any
13
+ from dataclasses import dataclass
14
+ from typing import Optional, Dict, Any, ClassVar
14
15
 
15
16
  from pygpt_net.item.ctx import CtxItem
16
17
 
17
- class BaseEvent:
18
18
 
19
- def __init__(
20
- self,
21
- name: Optional[str] = None,
22
- data: Optional[dict] = None,
23
- ctx: CtxItem = None
24
- ):
25
- """
26
- Base Event object class
19
+ @dataclass(slots=True)
20
+ class BaseEvent:
21
+ """Base Event object class"""
22
+ id: ClassVar[str] = None
23
+ name: Optional[str] = None
24
+ data: Optional[dict] = None
25
+ ctx: Optional[CtxItem] = None # CtxItem
26
+ stop: bool = False # True to stop propagation
27
+ internal: bool = False
28
+ call_id: int = 0
27
29
 
28
- :param name: event name
29
- :param data: event data
30
- :param ctx: context instance
31
- """
32
- self.id = None
33
- self.name = name
34
- self.data = data
30
+ def __post_init__(self):
31
+ # Normalize None to empty dict for convenience and safety
35
32
  if self.data is None:
36
33
  self.data = {}
37
- self.ctx = ctx # CtxItem
38
- self.stop = False # True to stop propagation
39
- self.internal = False
40
- self.call_id = 0
41
-
42
34
 
43
35
  @property
44
36
  def full_name(self) -> str:
@@ -47,7 +39,7 @@ class BaseEvent:
47
39
 
48
40
  :return: Full event name
49
41
  """
50
- return self.id + ": " + self.name
42
+ return self.id + ": " + self.name # type: ignore[operator]
51
43
 
52
44
  def to_dict(self) -> Dict[str, Any]:
53
45
  """Dump event to dict"""
@@ -66,7 +58,7 @@ class BaseEvent:
66
58
  """
67
59
  try:
68
60
  return json.dumps(self.to_dict())
69
- except Exception as e:
61
+ except Exception:
70
62
  pass
71
63
  return ""
72
64