pygpt-net 2.6.1__py3-none-any.whl → 2.6.6__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 (131) hide show
  1. pygpt_net/CHANGELOG.txt +23 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +20 -1
  4. pygpt_net/config.py +55 -65
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/calendar/note.py +101 -126
  7. pygpt_net/controller/chat/chat.py +38 -35
  8. pygpt_net/controller/chat/render.py +154 -214
  9. pygpt_net/controller/chat/response.py +5 -3
  10. pygpt_net/controller/chat/stream.py +92 -27
  11. pygpt_net/controller/config/config.py +39 -42
  12. pygpt_net/controller/config/field/checkbox.py +16 -12
  13. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  14. pygpt_net/controller/config/field/cmd.py +51 -57
  15. pygpt_net/controller/config/field/combo.py +33 -16
  16. pygpt_net/controller/config/field/dictionary.py +48 -55
  17. pygpt_net/controller/config/field/input.py +50 -32
  18. pygpt_net/controller/config/field/slider.py +40 -45
  19. pygpt_net/controller/config/field/textarea.py +20 -6
  20. pygpt_net/controller/config/placeholder.py +110 -231
  21. pygpt_net/controller/ctx/common.py +48 -48
  22. pygpt_net/controller/ctx/ctx.py +91 -132
  23. pygpt_net/controller/lang/mapping.py +57 -95
  24. pygpt_net/controller/lang/plugins.py +64 -55
  25. pygpt_net/controller/lang/settings.py +39 -38
  26. pygpt_net/controller/layout/layout.py +176 -109
  27. pygpt_net/controller/mode/mode.py +88 -85
  28. pygpt_net/controller/model/model.py +73 -73
  29. pygpt_net/controller/plugins/plugins.py +209 -223
  30. pygpt_net/controller/plugins/presets.py +54 -55
  31. pygpt_net/controller/plugins/settings.py +54 -69
  32. pygpt_net/controller/presets/editor.py +33 -88
  33. pygpt_net/controller/presets/experts.py +20 -1
  34. pygpt_net/controller/presets/presets.py +293 -298
  35. pygpt_net/controller/settings/profile.py +16 -4
  36. pygpt_net/controller/theme/theme.py +72 -81
  37. pygpt_net/controller/ui/mode.py +118 -186
  38. pygpt_net/controller/ui/tabs.py +69 -90
  39. pygpt_net/controller/ui/ui.py +47 -56
  40. pygpt_net/controller/ui/vision.py +24 -23
  41. pygpt_net/core/agents/runner.py +15 -7
  42. pygpt_net/core/bridge/bridge.py +5 -5
  43. pygpt_net/core/command/command.py +149 -219
  44. pygpt_net/core/ctx/ctx.py +94 -146
  45. pygpt_net/core/debug/debug.py +48 -58
  46. pygpt_net/core/experts/experts.py +3 -3
  47. pygpt_net/core/models/models.py +74 -112
  48. pygpt_net/core/modes/modes.py +13 -21
  49. pygpt_net/core/plugins/plugins.py +154 -177
  50. pygpt_net/core/presets/presets.py +103 -176
  51. pygpt_net/core/render/web/body.py +217 -215
  52. pygpt_net/core/render/web/renderer.py +330 -474
  53. pygpt_net/core/text/utils.py +28 -44
  54. pygpt_net/core/tokens/tokens.py +104 -203
  55. pygpt_net/data/config/config.json +3 -3
  56. pygpt_net/data/config/models.json +3 -3
  57. pygpt_net/data/locale/locale.de.ini +2 -0
  58. pygpt_net/data/locale/locale.en.ini +2 -0
  59. pygpt_net/data/locale/locale.es.ini +2 -0
  60. pygpt_net/data/locale/locale.fr.ini +2 -0
  61. pygpt_net/data/locale/locale.it.ini +2 -0
  62. pygpt_net/data/locale/locale.pl.ini +3 -1
  63. pygpt_net/data/locale/locale.uk.ini +2 -0
  64. pygpt_net/data/locale/locale.zh.ini +2 -0
  65. pygpt_net/item/ctx.py +141 -139
  66. pygpt_net/plugin/agent/plugin.py +2 -1
  67. pygpt_net/plugin/audio_output/plugin.py +5 -2
  68. pygpt_net/plugin/base/plugin.py +101 -85
  69. pygpt_net/plugin/bitbucket/__init__.py +12 -0
  70. pygpt_net/plugin/bitbucket/config.py +267 -0
  71. pygpt_net/plugin/bitbucket/plugin.py +126 -0
  72. pygpt_net/plugin/bitbucket/worker.py +569 -0
  73. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  74. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  75. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  76. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  77. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  78. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  79. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  80. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  81. pygpt_net/plugin/experts/plugin.py +2 -2
  82. pygpt_net/plugin/facebook/__init__.py +12 -0
  83. pygpt_net/plugin/facebook/config.py +359 -0
  84. pygpt_net/plugin/facebook/plugin.py +113 -0
  85. pygpt_net/plugin/facebook/worker.py +698 -0
  86. pygpt_net/plugin/github/__init__.py +12 -0
  87. pygpt_net/plugin/github/config.py +441 -0
  88. pygpt_net/plugin/github/plugin.py +126 -0
  89. pygpt_net/plugin/github/worker.py +674 -0
  90. pygpt_net/plugin/google/__init__.py +12 -0
  91. pygpt_net/plugin/google/config.py +367 -0
  92. pygpt_net/plugin/google/plugin.py +126 -0
  93. pygpt_net/plugin/google/worker.py +826 -0
  94. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  95. pygpt_net/plugin/mailer/plugin.py +3 -5
  96. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  97. pygpt_net/plugin/real_time/plugin.py +52 -60
  98. pygpt_net/plugin/slack/__init__.py +12 -0
  99. pygpt_net/plugin/slack/config.py +349 -0
  100. pygpt_net/plugin/slack/plugin.py +115 -0
  101. pygpt_net/plugin/slack/worker.py +639 -0
  102. pygpt_net/plugin/telegram/__init__.py +12 -0
  103. pygpt_net/plugin/telegram/config.py +308 -0
  104. pygpt_net/plugin/telegram/plugin.py +117 -0
  105. pygpt_net/plugin/telegram/worker.py +563 -0
  106. pygpt_net/plugin/twitter/__init__.py +12 -0
  107. pygpt_net/plugin/twitter/config.py +491 -0
  108. pygpt_net/plugin/twitter/plugin.py +125 -0
  109. pygpt_net/plugin/twitter/worker.py +837 -0
  110. pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
  111. pygpt_net/tools/code_interpreter/tool.py +0 -1
  112. pygpt_net/tools/translator/tool.py +1 -1
  113. pygpt_net/ui/base/config_dialog.py +86 -100
  114. pygpt_net/ui/base/context_menu.py +48 -46
  115. pygpt_net/ui/dialog/preset.py +34 -77
  116. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  117. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  118. pygpt_net/ui/main.py +49 -31
  119. pygpt_net/ui/tray.py +61 -60
  120. pygpt_net/ui/widget/calendar/select.py +86 -70
  121. pygpt_net/ui/widget/lists/attachment.py +86 -44
  122. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  123. pygpt_net/ui/widget/lists/context.py +135 -188
  124. pygpt_net/ui/widget/lists/preset.py +59 -61
  125. pygpt_net/ui/widget/textarea/web.py +161 -48
  126. pygpt_net/utils.py +8 -1
  127. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/METADATA +164 -2
  128. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/RECORD +131 -103
  129. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/LICENSE +0 -0
  130. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/WHEEL +0 -0
  131. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/entry_points.txt +0 -0
@@ -171,8 +171,9 @@ class Response:
171
171
  if output and has_unclosed_code_tag(output):
172
172
  ctx.output += "\n```"
173
173
  ctx.msg_id = None
174
- self.window.core.ctx.add(ctx) # store context to prevent current output from being lost
175
- self.window.controller.ctx.prepare_name(ctx) # summarize if not yet
174
+ if ctx.id is None:
175
+ self.window.core.ctx.add(ctx) # store context to prevent current output from being lost
176
+ self.window.controller.ctx.prepare_name(ctx) # summarize if not yet
176
177
 
177
178
  # finish render
178
179
  self.window.dispatch(AppEvent(AppEvent.CTX_END)) # app event
@@ -228,7 +229,8 @@ class Response:
228
229
  }
229
230
  event = RenderEvent(RenderEvent.INPUT_APPEND, data)
230
231
  self.window.dispatch(event)
231
- self.window.core.ctx.add(ctx)
232
+ if ctx.id is None:
233
+ self.window.core.ctx.add(ctx)
232
234
  self.window.controller.ctx.update(
233
235
  reload=True,
234
236
  all=False,
@@ -6,11 +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: 2025.08.11 14:00:00 #
9
+ # Updated Date: 2025.08.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
13
- from typing import Optional
13
+ import gc
14
+ import io
15
+ from typing import Optional, Literal
14
16
 
15
17
  from PySide6.QtCore import QObject, Signal, Slot, QRunnable
16
18
 
@@ -20,6 +22,32 @@ from pygpt_net.core.types import MODE_ASSISTANT
20
22
  from pygpt_net.core.text.utils import has_unclosed_code_tag
21
23
  from pygpt_net.item.ctx import CtxItem
22
24
 
25
+ EventType = Literal[
26
+ "response.completed",
27
+ "response.output_text.delta",
28
+ "response.output_item.added",
29
+ "response.function_call_arguments.delta",
30
+ "response.function_call_arguments.done",
31
+ "response.output_text.annotation.added",
32
+ "response.reasoning_summary_text.delta",
33
+ "response.output_item.done",
34
+ "response.code_interpreter_call_code.delta",
35
+ "response.code_interpreter_call_code.done",
36
+ "response.image_generation_call.partial_image",
37
+ "response.created",
38
+ "response.done",
39
+ "response.failed",
40
+ "error",
41
+ ]
42
+ ChunkType = Literal[
43
+ "api_chat",
44
+ "api_chat_responses",
45
+ "api_completion",
46
+ "langchain_chat",
47
+ "llama_chat",
48
+ "raw",
49
+ ]
50
+
23
51
  class WorkerSignals(QObject):
24
52
  """
25
53
  Defines the signals available from a running worker thread.
@@ -54,15 +82,15 @@ class StreamWorker(QRunnable):
54
82
  output_tokens = 0
55
83
  begin = True
56
84
  error = None
57
- fn_args_buffers = {}
58
- citations = []
85
+ fn_args_buffers: dict[str, io.StringIO] = {}
86
+ citations: Optional[list] = []
59
87
  files = []
60
88
  img_path = core.image.gen_unique_path(ctx)
61
89
  is_image = False
62
90
  is_code = False
63
91
  force_func_call = False
64
92
  stopped = False
65
- chunk_type = "raw"
93
+ chunk_type: ChunkType = "raw"
66
94
  generator = ctx.stream
67
95
  ctx.stream = None
68
96
 
@@ -77,6 +105,21 @@ class StreamWorker(QRunnable):
77
105
  if generator is not None:
78
106
  for chunk in generator:
79
107
  if ctrl.kernel.stopped():
108
+ if hasattr(generator, 'close'):
109
+ try:
110
+ generator.close()
111
+ except Exception:
112
+ pass
113
+ elif hasattr(generator, 'cancel'):
114
+ try:
115
+ generator.cancel()
116
+ except Exception:
117
+ pass
118
+ elif hasattr(generator, 'stop'):
119
+ try:
120
+ generator.stop()
121
+ except Exception:
122
+ pass
80
123
  ctx.msg_id = None
81
124
  stopped = True
82
125
  break
@@ -86,12 +129,12 @@ class StreamWorker(QRunnable):
86
129
  stopped = True
87
130
  break
88
131
 
89
- etype = None
132
+ etype: Optional[EventType] = None
90
133
  response = None
91
134
 
92
135
  if ctx.use_responses_api:
93
136
  if hasattr(chunk, 'type'):
94
- etype = chunk.type
137
+ etype = chunk.type # type: ignore[assignment]
95
138
  chunk_type = "api_chat_responses"
96
139
  else:
97
140
  continue
@@ -113,7 +156,6 @@ class StreamWorker(QRunnable):
113
156
  else:
114
157
  chunk_type = "raw"
115
158
 
116
- # OpenAI chat completion
117
159
  if chunk_type == "api_chat":
118
160
  citations = None
119
161
  delta = chunk.choices[0].delta
@@ -143,7 +185,6 @@ class StreamWorker(QRunnable):
143
185
  if getattr(tool_chunk.function, "arguments", None):
144
186
  tool_call["function"]["arguments"] += tool_chunk.function.arguments
145
187
 
146
- # OpenAI Responses API
147
188
  elif chunk_type == "api_chat_responses":
148
189
  if etype == "response.completed":
149
190
  for item in chunk.response.output:
@@ -182,7 +223,6 @@ class StreamWorker(QRunnable):
182
223
  elif etype == "response.output_text.delta":
183
224
  response = chunk.delta
184
225
 
185
- # function_call
186
226
  elif etype == "response.output_item.added" and chunk.item.type == "function_call":
187
227
  tool_calls.append({
188
228
  "id": chunk.item.id,
@@ -190,18 +230,23 @@ class StreamWorker(QRunnable):
190
230
  "type": "function",
191
231
  "function": {"name": chunk.item.name, "arguments": ""}
192
232
  })
193
- fn_args_buffers[chunk.item.id] = ""
233
+ fn_args_buffers[chunk.item.id] = io.StringIO()
194
234
  elif etype == "response.function_call_arguments.delta":
195
- fn_args_buffers[chunk.item_id] += chunk.delta
235
+ buf = fn_args_buffers.get(chunk.item_id)
236
+ if buf is not None:
237
+ buf.write(chunk.delta)
196
238
  elif etype == "response.function_call_arguments.done":
197
239
  buf = fn_args_buffers.pop(chunk.item_id, None)
198
240
  if buf is not None:
241
+ try:
242
+ args_val = buf.getvalue()
243
+ finally:
244
+ buf.close()
199
245
  for tc in tool_calls:
200
246
  if tc["id"] == chunk.item_id:
201
- tc["function"]["arguments"] = buf
247
+ tc["function"]["arguments"] = args_val
202
248
  break
203
249
 
204
- # annotations
205
250
  elif etype == "response.output_text.annotation.added":
206
251
  ann = chunk.annotation
207
252
  if ann['type'] == "url_citation":
@@ -216,7 +261,6 @@ class StreamWorker(QRunnable):
216
261
  "file_id": ann['file_id'],
217
262
  })
218
263
 
219
- # computer use
220
264
  elif etype == "response.reasoning_summary_text.delta":
221
265
  response = chunk.delta
222
266
 
@@ -225,7 +269,6 @@ class StreamWorker(QRunnable):
225
269
  if has_calls:
226
270
  force_func_call = True
227
271
 
228
- # code interpreter
229
272
  elif etype == "response.code_interpreter_call_code.delta":
230
273
  if not is_code:
231
274
  response = "\n\n**Code interpreter**\n```python\n" + chunk.delta
@@ -235,36 +278,30 @@ class StreamWorker(QRunnable):
235
278
  elif etype == "response.code_interpreter_call_code.done":
236
279
  response = "\n\n```\n-----------\n"
237
280
 
238
- # image gen
239
281
  elif etype == "response.image_generation_call.partial_image":
240
282
  image_base64 = chunk.partial_image_b64
241
283
  image_bytes = base64.b64decode(image_base64)
242
- # prosty i bezpieczny overwrite (jak w oryginale)
243
284
  with open(img_path, "wb") as f:
244
285
  f.write(image_bytes)
286
+ del image_bytes
245
287
  is_image = True
246
288
 
247
- # response ID
248
289
  elif etype == "response.created":
249
290
  ctx.msg_id = str(chunk.response.id)
250
291
  core.ctx.update_item(ctx)
251
292
 
252
- # end/error etype – nic nie robimy
253
293
  elif etype in {"response.done", "response.failed", "error"}:
254
294
  pass
255
295
 
256
- # OpenAI completion
257
296
  elif chunk_type == "api_completion":
258
297
  choice0 = chunk.choices[0]
259
298
  if choice0.text is not None:
260
299
  response = choice0.text
261
300
 
262
- # langchain chat
263
301
  elif chunk_type == "langchain_chat":
264
302
  if chunk.content is not None:
265
303
  response = str(chunk.content)
266
304
 
267
- # llama chat
268
305
  elif chunk_type == "llama_chat":
269
306
  if chunk.delta is not None:
270
307
  response = str(chunk.delta)
@@ -288,7 +325,6 @@ class StreamWorker(QRunnable):
288
325
  tool_calls.clear()
289
326
  tool_calls.append(tool_call)
290
327
 
291
- # raw text (llama-index / langchain completion)
292
328
  else:
293
329
  if chunk is not None:
294
330
  response = str(chunk)
@@ -313,13 +349,11 @@ class StreamWorker(QRunnable):
313
349
 
314
350
  chunk = None
315
351
 
316
- # tool calls
317
352
  if tool_calls:
318
353
  ctx.force_call = force_func_call
319
354
  core.debug.info("[chat] Tool calls found, unpacking...")
320
355
  core.command.unpack_tool_calls_chunks(ctx, tool_calls)
321
356
 
322
- # image
323
357
  if is_image:
324
358
  core.debug.info("[chat] Image generation call found")
325
359
  ctx.images = [img_path]
@@ -358,11 +392,17 @@ class StreamWorker(QRunnable):
358
392
 
359
393
  emit_end(ctx)
360
394
 
395
+ for _buf in fn_args_buffers.values():
396
+ try:
397
+ _buf.close()
398
+ except Exception:
399
+ pass
361
400
  fn_args_buffers.clear()
362
401
  files.clear()
363
402
  tool_calls.clear()
364
- if citations is not None:
403
+ if citations is not None and citations is not ctx.urls:
365
404
  citations.clear()
405
+ citations = None
366
406
 
367
407
  self.cleanup()
368
408
 
@@ -407,6 +447,14 @@ class Stream:
407
447
  ):
408
448
  """
409
449
  Asynchronous append of stream worker to the thread.
450
+
451
+ :param ctx: Context item
452
+ :param mode: Mode of operation (e.g., MODE_ASSISTANT)
453
+ :param is_response: Whether this is a response stream
454
+ :param reply: Reply identifier
455
+ :param internal: Whether this is an internal stream
456
+ :param context: Optional BridgeContext for additional context
457
+ :param extra: Additional data to pass to the stream
410
458
  """
411
459
  self.ctx = ctx
412
460
  self.mode = mode
@@ -430,6 +478,8 @@ class Stream:
430
478
  def handleEnd(self, ctx: CtxItem):
431
479
  """
432
480
  Slot for handling end of stream
481
+
482
+ :param ctx: Context item
433
483
  """
434
484
  self.window.controller.ui.update_tokens()
435
485
 
@@ -457,9 +507,19 @@ class Stream:
457
507
  self.worker = None
458
508
 
459
509
  def handleEvent(self, event):
510
+ """
511
+ Slot for handling stream events
512
+
513
+ :param event: RenderEvent
514
+ """
460
515
  self.window.dispatch(event)
461
516
 
462
517
  def handleError(self, error):
518
+ """
519
+ Slot for handling stream errors
520
+
521
+ :param error: Exception or error message
522
+ """
463
523
  self.window.core.debug.log(error)
464
524
  if self.is_response:
465
525
  if not isinstance(self.extra, dict):
@@ -475,4 +535,9 @@ class Stream:
475
535
  )
476
536
 
477
537
  def log(self, data: object):
538
+ """
539
+ Log data to the debug console
540
+
541
+ :param data: object to log
542
+ """
478
543
  self.window.core.debug.info(data)
@@ -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.06.30 02:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Dict, List
@@ -40,6 +40,24 @@ class Config:
40
40
  self.slider = Slider(window)
41
41
  self.textarea = Textarea(window)
42
42
 
43
+ self._apply_map = {
44
+ 'text': self.input.apply,
45
+ 'textarea': self.textarea.apply,
46
+ 'bool': self.checkbox.apply,
47
+ 'bool_list': self.checkbox_list.apply,
48
+ 'dict': self.dictionary.apply,
49
+ 'combo': self.combo.apply,
50
+ 'cmd': self.cmd.apply,
51
+ }
52
+ self._get_map = {
53
+ 'text': self.input.get_value,
54
+ 'textarea': self.textarea.get_value,
55
+ 'bool': self.checkbox.get_value,
56
+ 'bool_list': self.checkbox_list.get_value,
57
+ 'dict': self.dictionary.get_value,
58
+ 'cmd': self.cmd.get_value,
59
+ }
60
+
43
61
  def load_options(
44
62
  self,
45
63
  parent_id: str,
@@ -51,8 +69,7 @@ class Config:
51
69
  :param parent_id: Parent ID
52
70
  :param options: Options dict
53
71
  """
54
- for key in options:
55
- option = options[key]
72
+ for key, option in options.items():
56
73
  self.apply(parent_id, key, option)
57
74
 
58
75
  def apply(
@@ -68,25 +85,16 @@ class Config:
68
85
  :param key: Option key
69
86
  :param option: Option dict
70
87
  """
71
- if option['type'] == 'int' or option['type'] == 'float':
72
- if 'slider' in option and option['slider']:
88
+ t = option['type']
89
+ if t in ('int', 'float'):
90
+ if option.get('slider'):
73
91
  self.slider.apply(parent_id, key, option)
74
92
  else:
75
93
  self.input.apply(parent_id, key, option)
76
- elif option['type'] == 'text':
77
- self.input.apply(parent_id, key, option)
78
- elif option['type'] == 'textarea':
79
- self.textarea.apply(parent_id, key, option)
80
- elif option['type'] == 'bool':
81
- self.checkbox.apply(parent_id, key, option)
82
- elif option['type'] == 'bool_list':
83
- self.checkbox_list.apply(parent_id, key, option)
84
- elif option['type'] == 'dict':
85
- self.dictionary.apply(parent_id, key, option)
86
- elif option['type'] == 'combo':
87
- self.combo.apply(parent_id, key, option)
88
- elif option['type'] == 'cmd':
89
- self.cmd.apply(parent_id, key, option)
94
+ return
95
+ func = self._apply_map.get(t)
96
+ if func:
97
+ func(parent_id, key, option)
90
98
 
91
99
  def apply_value(
92
100
  self,
@@ -122,25 +130,16 @@ class Config:
122
130
  :param idx: return selected idx, not the value
123
131
  :return: Option value
124
132
  """
125
- if option['type'] == 'int' or option['type'] == 'float':
126
- if 'slider' in option and option['slider']:
133
+ t = option['type']
134
+ if t in ('int', 'float'):
135
+ if option.get('slider'):
127
136
  return self.slider.get_value(parent_id, key, option)
128
- else:
129
- return self.input.get_value(parent_id, key, option)
130
- elif option['type'] == 'text':
131
137
  return self.input.get_value(parent_id, key, option)
132
- elif option['type'] == 'textarea':
133
- return self.textarea.get_value(parent_id, key, option)
134
- elif option['type'] == 'bool':
135
- return self.checkbox.get_value(parent_id, key, option)
136
- elif option['type'] == 'bool_list':
137
- return self.checkbox_list.get_value(parent_id, key, option)
138
- elif option['type'] == 'dict':
139
- return self.dictionary.get_value(parent_id, key, option)
140
- elif option['type'] == 'combo':
138
+ if t == 'combo':
141
139
  return self.combo.get_value(parent_id, key, option, idx)
142
- elif option['type'] == 'cmd':
143
- return self.cmd.get_value(parent_id, key, option)
140
+ func = self._get_map.get(t)
141
+ if func:
142
+ return func(parent_id, key, option)
144
143
 
145
144
  def update_list(
146
145
  self,
@@ -159,13 +158,11 @@ class Config:
159
158
  """
160
159
  if "type" not in option:
161
160
  return
162
- if option['type'] == 'combo':
163
- as_dict = {}
164
- for item in items:
165
- for k, v in item.items():
166
- as_dict[k] = v
161
+ t = option['type']
162
+ if t == 'combo':
163
+ as_dict = {k: v for d in items for k, v in d.items()}
167
164
  self.update_combo(parent_id, key, as_dict)
168
- elif option['type'] == 'bool_list':
165
+ elif t == 'bool_list':
169
166
  self.update_bool_list(parent_id, key, items)
170
167
 
171
168
  def update_combo(
@@ -196,4 +193,4 @@ class Config:
196
193
  :param key: Option key
197
194
  :param items: Items dict
198
195
  """
199
- self.checkbox_list.update_list(parent_id, key, items)
196
+ self.checkbox_list.update_list(parent_id, key, items)
@@ -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.06.29 18:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Dict
@@ -26,7 +26,7 @@ class Checkbox:
26
26
  parent_id: str,
27
27
  key: str,
28
28
  option: Dict[str, Any]
29
- ):
29
+ ) -> None:
30
30
  """
31
31
  Apply value to checkbox
32
32
 
@@ -39,17 +39,19 @@ class Checkbox:
39
39
  value = option["value"]
40
40
  if value is None:
41
41
  value = False
42
- if key in self.window.ui.config[parent_id]:
43
- self.window.ui.config[parent_id][key].box.setChecked(bool(value))
42
+ cfg_parent = self.window.ui.config[parent_id]
43
+ row = cfg_parent.get(key)
44
+ if row is not None:
45
+ row.box.setChecked(bool(value))
44
46
 
45
47
  def on_update(
46
48
  self,
47
49
  parent_id: str,
48
50
  key: str,
49
- option: dict,
51
+ option: Dict[str, Any],
50
52
  value: Any,
51
53
  hooks: bool = True
52
- ):
54
+ ) -> None:
53
55
  """
54
56
  Event: on update checkbox value
55
57
 
@@ -59,11 +61,11 @@ class Checkbox:
59
61
  :param value: Option value
60
62
  :param hooks: Run hooks
61
63
  """
62
- # on update hooks
63
64
  if hooks:
64
- hook_name = "update.{}.{}".format(parent_id, key)
65
- if self.window.ui.has_hook(hook_name):
66
- hook = self.window.ui.get_hook(hook_name)
65
+ ui = self.window.ui
66
+ hook_name = f"update.{parent_id}.{key}"
67
+ if ui.has_hook(hook_name):
68
+ hook = ui.get_hook(hook_name)
67
69
  try:
68
70
  hook(key, value, 'checkbox')
69
71
  except Exception as e:
@@ -83,6 +85,8 @@ class Checkbox:
83
85
  :param option: Option data dict
84
86
  :return: Option value
85
87
  """
86
- if key not in self.window.ui.config[parent_id]:
88
+ cfg_parent = self.window.ui.config[parent_id]
89
+ row = cfg_parent.get(key)
90
+ if row is None:
87
91
  return False
88
- return self.window.ui.config[parent_id][key].box.isChecked()
92
+ return row.box.isChecked()
@@ -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.06.30 02:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Dict, List
@@ -37,19 +37,24 @@ class CheckboxList:
37
37
  if "value" not in option:
38
38
  return
39
39
  value = option["value"]
40
- exploded_list = value.split(",") if isinstance(value, str) else []
41
- if key not in self.window.ui.config[parent_id]:
42
- self.window.ui.config[parent_id][key] = {}
43
- for item in self.window.ui.config[parent_id][key].boxes:
44
- if self.window.ui.config[parent_id][key].boxes[item] is not None:
45
- self.window.ui.config[parent_id][key].boxes[item].setChecked(False)
46
- for item in exploded_list:
47
- item = item.strip()
48
- if item not in self.window.ui.config[parent_id][key].boxes:
49
- continue
50
- if self.window.ui.config[parent_id][key].boxes[item] is None:
40
+ selection = {s.strip() for s in value.split(",")} if isinstance(value, str) else set()
41
+ selection.discard("")
42
+
43
+ ui = self.window.ui
44
+ cfg_parent = ui.config.get(parent_id)
45
+ if not cfg_parent:
46
+ return
47
+ entry = cfg_parent.get(key)
48
+ if entry is None or not hasattr(entry, "boxes"):
49
+ return
50
+ boxes = entry.boxes
51
+
52
+ for name, cb in boxes.items():
53
+ if cb is None:
51
54
  continue
52
- self.window.ui.config[parent_id][key].boxes[item].setChecked(True)
55
+ desired = name in selection
56
+ if cb.isChecked() != desired:
57
+ cb.setChecked(desired)
53
58
 
54
59
  def on_update(
55
60
  self,
@@ -70,15 +75,16 @@ class CheckboxList:
70
75
  :param subkey: Subkey for specific checkbox
71
76
  :param hooks: Run hooks
72
77
  """
73
- # on update hooks
74
78
  if hooks:
75
- hook_name = "update.{}.{}".format(parent_id, key)
76
- if self.window.ui.has_hook(hook_name):
77
- hook = self.window.ui.get_hook(hook_name)
78
- try:
79
- hook(key, value, 'bool_list')
80
- except Exception as e:
81
- self.window.core.debug.log(e)
79
+ ui = self.window.ui
80
+ hook_name = f"update.{parent_id}.{key}"
81
+ if ui.has_hook(hook_name):
82
+ hook = ui.get_hook(hook_name)
83
+ if hook:
84
+ try:
85
+ hook(key, value, 'bool_list')
86
+ except Exception as e:
87
+ self.window.core.debug.log(e)
82
88
 
83
89
  def get_value(
84
90
  self,
@@ -94,16 +100,15 @@ class CheckboxList:
94
100
  :param option: Option data dict
95
101
  :return: Option value
96
102
  """
97
- if key not in self.window.ui.config[parent_id]:
103
+ ui = self.window.ui
104
+ cfg_parent = ui.config.get(parent_id)
105
+ if not cfg_parent:
98
106
  return ""
99
- imploded_list = []
100
- for item in self.window.ui.config[parent_id][key].boxes:
101
- if self.window.ui.config[parent_id][key].boxes[item] is None:
102
- continue
103
- if self.window.ui.config[parent_id][key].boxes[item].isChecked():
104
- imploded_list.append(item)
105
- return ",".join(imploded_list)
106
-
107
+ entry = cfg_parent.get(key)
108
+ if entry is None or not hasattr(entry, "boxes"):
109
+ return ""
110
+ boxes = entry.boxes
111
+ return ",".join(name for name, cb in boxes.items() if cb is not None and cb.isChecked())
107
112
 
108
113
  def update_list(
109
114
  self,
@@ -118,4 +123,4 @@ class CheckboxList:
118
123
  :param key: Option key
119
124
  :param items: Items dict
120
125
  """
121
- self.window.ui.config[parent_id][key].update_boxes_list(items)
126
+ self.window.ui.config[parent_id][key].update_boxes_list(items)