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
@@ -6,11 +6,11 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 08:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
- import copy
13
- from typing import Dict
12
+ from copy import deepcopy
13
+ from typing import Dict, Optional
14
14
  from uuid import uuid4
15
15
 
16
16
  from PySide6.QtGui import QAction
@@ -30,10 +30,11 @@ class Presets:
30
30
  self.dialog = False
31
31
  self.width = 500
32
32
  self.height = 500
33
+ self._sep_action = None
33
34
 
34
35
  def setup(self):
35
36
  """Set up plugin presets"""
36
- self.window.plugin_presets.setup() # widget dialog
37
+ self.window.plugin_presets.setup()
37
38
 
38
39
  def new(self):
39
40
  """New preset dialog"""
@@ -63,8 +64,6 @@ class Presets:
63
64
  self.current_to_preset(id)
64
65
  self.window.ui.dialog['create'].close()
65
66
  self.update_list()
66
- self.update_menu()
67
-
68
67
  self.toggle(id)
69
68
 
70
69
  def open(self):
@@ -100,6 +99,8 @@ class Presets:
100
99
  :param idx: preset index
101
100
  """
102
101
  id = self.get_id_by_idx(idx)
102
+ if id is None:
103
+ return
103
104
  self.rename(id)
104
105
 
105
106
  def rename(self, id: str):
@@ -113,7 +114,6 @@ class Presets:
113
114
  if id in presets:
114
115
  name = presets[id]['name']
115
116
 
116
- # set dialog and show
117
117
  self.window.ui.dialog['rename'].id = 'plugin.preset'
118
118
  self.window.ui.dialog['rename'].input.setText(name)
119
119
  self.window.ui.dialog['rename'].current = id
@@ -148,7 +148,6 @@ class Presets:
148
148
  del presets[id]
149
149
  self.store(presets)
150
150
 
151
- # remove current preset if exists
152
151
  current = self.window.core.config.get('preset.plugins')
153
152
  if current == id:
154
153
  self.window.core.config.set('preset.plugins', '')
@@ -166,15 +165,16 @@ class Presets:
166
165
  self.window.core.plugins.replace_presets(presets)
167
166
  self.window.core.plugins.save_presets()
168
167
 
169
- def get_id_by_idx(self, idx: int) -> str:
168
+ def get_id_by_idx(self, idx: int) -> Optional[str]:
170
169
  """
171
170
  Get preset id by index
172
171
 
173
172
  :param idx: preset index
174
173
  """
175
174
  presets = self.get_presets()
176
- if len(presets) > idx:
175
+ if 0 <= idx < len(presets):
177
176
  return list(presets.keys())[idx]
177
+ return None
178
178
 
179
179
  def select_by_idx(self, idx: int):
180
180
  """
@@ -183,8 +183,9 @@ class Presets:
183
183
  :param idx: preset index
184
184
  """
185
185
  id = self.get_id_by_idx(idx)
186
+ if id is None:
187
+ return
186
188
  self.toggle(id)
187
- self.update_menu()
188
189
 
189
190
  def delete_by_idx(
190
191
  self,
@@ -205,6 +206,8 @@ class Presets:
205
206
  )
206
207
  return
207
208
  id = self.get_id_by_idx(idx)
209
+ if id is None:
210
+ return
208
211
  self.delete(id)
209
212
 
210
213
  def duplicate(self, id: str):
@@ -214,7 +217,7 @@ class Presets:
214
217
  :param id: preset ID
215
218
  """
216
219
  presets = self.get_presets()
217
- duplicate = copy.deepcopy(presets[id])
220
+ duplicate = deepcopy(presets[id])
218
221
  new_id = str(uuid4())
219
222
  duplicate['name'] = duplicate['name'] + " - copy"
220
223
  presets[new_id] = duplicate
@@ -229,6 +232,8 @@ class Presets:
229
232
  :param idx: preset index
230
233
  """
231
234
  id = self.get_id_by_idx(idx)
235
+ if id is None:
236
+ return
232
237
  self.duplicate(id)
233
238
 
234
239
  def reset(self, id: str):
@@ -265,6 +270,8 @@ class Presets:
265
270
  )
266
271
  return
267
272
  id = self.get_id_by_idx(idx)
273
+ if id is None:
274
+ return
268
275
  self.reset(id)
269
276
 
270
277
  def get_preset(self, id: str) -> Dict:
@@ -285,13 +292,10 @@ class Presets:
285
292
  :return: presets dict
286
293
  """
287
294
  presets = self.window.core.plugins.get_presets()
288
- if not isinstance(presets, dict):
289
- presets = {}
290
- return presets
291
-
292
- # sort by name
295
+ if not isinstance(presets, dict) or not presets:
296
+ return {}
293
297
  try:
294
- return dict(sorted(presets.items(), key=lambda item: item[1]['name']))
298
+ return dict(sorted(presets.items(), key=lambda item: item[1].get('name', '')))
295
299
  except Exception as e:
296
300
  self.window.core.debug.log("Error while sorting presets")
297
301
  self.window.core.debug.error(e)
@@ -305,42 +309,41 @@ class Presets:
305
309
  def update_menu(self):
306
310
  """Update presets menu"""
307
311
  presets = self.get_presets()
312
+ menu_store = self.window.ui.menu
313
+ menu = menu_store['menu.plugins.presets']
308
314
 
309
- # clear presets
310
- for id in self.window.ui.menu['plugins_presets']:
311
- self.window.ui.menu['menu.plugins.presets'].removeAction(self.window.ui.menu['plugins_presets'][id])
315
+ old_actions = list(menu_store['plugins_presets'].values())
316
+ for act in old_actions:
317
+ menu.removeAction(act)
318
+ act.deleteLater()
319
+ menu_store['plugins_presets'].clear()
312
320
 
313
- # remove separator if exists
314
- if len(self.window.ui.menu['menu.plugins.presets'].actions()) > 2:
315
- self.window.ui.menu['menu.plugins.presets'].actions()[-1].deleteLater()
321
+ if self._sep_action is not None:
322
+ menu.removeAction(self._sep_action)
323
+ self._sep_action.deleteLater()
324
+ self._sep_action = None
316
325
 
317
- # add separator
318
326
  if len(presets) > 0:
319
- self.window.ui.menu['menu.plugins.presets'].addSeparator()
320
-
321
- # add presets
322
- for id in presets:
323
- preset = presets[id]
324
- self.window.ui.menu['plugins_presets'][id] = QAction(preset['name'], self.window, checkable=True)
325
- self.window.ui.menu['plugins_presets'][id].triggered.connect(
326
- lambda checked=None,
327
- id=id: self.window.controller.plugins.presets.toggle(id))
328
- self.window.ui.menu['plugins_presets'][id].setMenuRole(QAction.MenuRole.NoRole)
329
- self.window.ui.menu['menu.plugins.presets'].addAction(self.window.ui.menu['plugins_presets'][id])
330
-
331
- # update current preset
327
+ self._sep_action = menu.addSeparator()
328
+
329
+ for id, preset in presets.items():
330
+ action = QAction(preset['name'], menu, checkable=True)
331
+ action.triggered.connect(lambda checked=False, id=id: self.window.controller.plugins.presets.toggle(id))
332
+ action.setMenuRole(QAction.MenuRole.NoRole)
333
+ menu.addAction(action)
334
+ menu_store['plugins_presets'][id] = action
335
+
332
336
  self.update()
333
337
 
334
338
  def update(self):
335
339
  """Update presets menu"""
336
- # clear
337
- for preset in self.window.ui.menu['plugins_presets']:
338
- self.window.ui.menu['plugins_presets'][preset].setChecked(False)
340
+ presets_menu = self.window.ui.menu['plugins_presets']
341
+ for preset in presets_menu:
342
+ presets_menu[preset].setChecked(False)
339
343
 
340
- # set checked current preset
341
344
  preset = self.window.core.config.get('preset.plugins')
342
- if preset in self.window.ui.menu['plugins_presets']:
343
- self.window.ui.menu['plugins_presets'][preset].setChecked(True)
345
+ if preset in presets_menu:
346
+ presets_menu[preset].setChecked(True)
344
347
 
345
348
  def toggle(self, id: str):
346
349
  """
@@ -350,13 +353,11 @@ class Presets:
350
353
  """
351
354
  self.window.core.config.set('preset.plugins', id)
352
355
  self.window.core.config.save()
353
- self.update_menu()
356
+ self.update()
354
357
 
355
- # load preset to current settings
356
358
  self.preset_to_current()
357
- self.window.controller.plugins.reconfigure() # reconfigure plugins
359
+ self.window.controller.plugins.reconfigure()
358
360
 
359
- # update status
360
361
  preset = self.get_preset(id)
361
362
  if preset:
362
363
  self.window.update_status("Preset loaded: " + preset['name'])
@@ -383,12 +384,10 @@ class Presets:
383
384
  if id:
384
385
  preset = self.get_preset(id)
385
386
  if preset:
386
- self.window.core.config.set('plugins_enabled', copy.deepcopy(preset['enabled']))
387
- self.window.core.config.set('plugins', copy.deepcopy(preset['config']))
387
+ self.window.core.config.set('plugins_enabled', deepcopy(preset['enabled']))
388
+ self.window.core.config.set('plugins', deepcopy(preset['config']))
388
389
  self.window.core.config.save()
389
- self.window.core.plugins.apply_all_options() # apply restored config
390
-
391
- # if settings dialog opened then reinitialize
390
+ self.window.core.plugins.apply_all_options()
392
391
  if self.window.controller.plugins.settings.config_dialog:
393
392
  self.window.controller.plugins.settings.init()
394
393
 
@@ -400,8 +399,8 @@ class Presets:
400
399
  """
401
400
  presets = self.get_presets()
402
401
  if preset_id in presets:
403
- presets[preset_id]['enabled'] = copy.deepcopy(self.window.core.config.get('plugins_enabled'))
404
- presets[preset_id]['config'] = copy.deepcopy(self.window.core.config.get('plugins'))
402
+ presets[preset_id]['enabled'] = deepcopy(self.window.core.config.get('plugins_enabled'))
403
+ presets[preset_id]['config'] = deepcopy(self.window.core.config.get('plugins'))
405
404
  self.store(presets)
406
405
  self.update_list()
407
406
  self.update_menu()
@@ -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 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any
@@ -32,10 +32,9 @@ class Settings:
32
32
  def setup(self):
33
33
  """Set up plugin settings"""
34
34
  idx = None
35
- # restore previous selected or restored tab on dialog create
36
35
  if 'plugin.settings' in self.window.ui.tabs:
37
36
  idx = self.window.ui.tabs['plugin.settings'].currentIndex()
38
- self.window.plugin_settings.setup(idx) # widget dialog Plugins
37
+ self.window.plugin_settings.setup(idx)
39
38
 
40
39
  def toggle_editor(self):
41
40
  """Toggle plugin settings dialog"""
@@ -49,14 +48,15 @@ class Settings:
49
48
  if not self.config_initialized:
50
49
  self.setup()
51
50
  self.config_initialized = True
52
- if not self.config_dialog:
53
- self.init()
54
- self.window.ui.dialogs.open(
55
- 'plugin_settings',
56
- width=self.width,
57
- height=self.height
58
- )
59
- self.config_dialog = True
51
+ if self.config_dialog:
52
+ return
53
+ self.init()
54
+ self.window.ui.dialogs.open(
55
+ 'plugin_settings',
56
+ width=self.width,
57
+ height=self.height
58
+ )
59
+ self.config_dialog = True
60
60
 
61
61
  def open_plugin(self, id: str):
62
62
  """
@@ -69,20 +69,14 @@ class Settings:
69
69
 
70
70
  def init(self):
71
71
  """Initialize plugin settings options"""
72
- # select the first plugin on list if no plugin selected yet
73
- if self.current_plugin is None:
74
- if len(self.window.core.plugins.plugins) > 0:
75
- self.current_plugin = list(self.window.core.plugins.plugins.keys())[0]
76
-
77
- # assign plugins options to config dialog fields
78
- for id in self.window.core.plugins.plugins.keys():
79
- plugin = self.window.core.plugins.plugins[id]
80
- options = plugin.setup() # get plugin options
81
-
82
- # load and apply options to config dialog
83
- self.window.controller.config.load_options('plugin.' + id, options)
84
-
85
- self.window.controller.layout.restore_plugin_settings() # restore previous selected plugin tab
72
+ plugins = self.window.core.plugins.plugins
73
+ if self.current_plugin is None and plugins:
74
+ self.current_plugin = next(iter(plugins))
75
+ cfg = self.window.controller.config
76
+ for pid, plugin in plugins.items():
77
+ options = plugin.setup()
78
+ cfg.load_options(f'plugin.{pid}', options)
79
+ self.window.controller.layout.restore_plugin_settings()
86
80
 
87
81
  def refresh_option(self, id: str, key: str):
88
82
  """
@@ -91,57 +85,55 @@ class Settings:
91
85
  :param id: plugin id
92
86
  :param key: option key
93
87
  """
94
- if id not in self.window.core.plugins.plugins:
88
+ plugins = self.window.core.plugins.plugins
89
+ plugin = plugins.get(id)
90
+ if not plugin:
95
91
  return
96
- if key not in self.window.core.plugins.plugins[id].options:
92
+ options = plugin.options
93
+ if key not in options:
97
94
  return
98
- option = self.window.core.plugins.plugins[id].options[key] # get option
99
- self.window.controller.config.placeholder.apply(option) # update keys in option
100
- items = option["keys"] if "keys" in option else {}
95
+ option = options[key]
96
+ self.window.controller.config.placeholder.apply(option)
97
+ items = option.get("keys", {})
101
98
  self.window.controller.config.update_list(
102
99
  option=option,
103
- parent_id='plugin.' + id,
100
+ parent_id=f'plugin.{id}',
104
101
  key=key,
105
102
  items=items,
106
103
  )
107
104
 
108
105
  def save(self):
109
106
  """Save plugin settings"""
110
- for id in self.window.core.plugins.plugins.keys():
111
- plugin = self.window.core.plugins.plugins[id]
112
- options = plugin.setup() # get plugin options
113
-
114
- # add plugin to global config data if not exists
115
- if id not in self.window.core.config.get('plugins'):
116
- self.window.core.config.data['plugins'][id] = {}
117
-
118
- # update config with new values
119
- for key in options:
120
- value = self.window.controller.config.get_value(
121
- parent_id='plugin.' + id,
122
- key=key,
123
- option=options[key],
107
+ window = self.window
108
+ plugins = window.core.plugins.plugins
109
+ controller_cfg = window.controller.config
110
+ config_data = window.core.config.data
111
+ plugins_cfg = config_data.setdefault('plugins', {})
112
+
113
+ for pid, plugin in plugins.items():
114
+ options = plugin.setup()
115
+ dst = plugins_cfg.setdefault(pid, {})
116
+ for key, opt in options.items():
117
+ value = controller_cfg.get_value(
118
+ parent_id=f'plugin.{pid}',
119
+ key=key,
120
+ option=opt,
124
121
  )
125
- self.window.core.plugins.plugins[id].options[key]['value'] = value
126
- self.window.core.config.data['plugins'][id][key] = value
127
-
128
- # remove key from config if plugin option not exists
129
- for key in list(self.window.core.config.data['plugins'].keys()):
130
- if key not in self.window.core.plugins.plugins:
131
- self.window.core.config.data['plugins'].pop(key)
122
+ plugin.options[key]['value'] = value
123
+ dst[key] = value
132
124
 
133
- # save preset
134
- self.window.controller.plugins.presets.save_current()
125
+ stale = set(plugins_cfg.keys()) - set(plugins.keys())
126
+ for pid in stale:
127
+ plugins_cfg.pop(pid, None)
135
128
 
136
- # save config
137
- self.window.core.config.save()
129
+ window.controller.plugins.presets.save_current()
130
+ window.core.config.save()
138
131
  self.close()
139
- self.window.update_status(trans('info.settings.saved'))
132
+ window.update_status(trans('info.settings.saved'))
140
133
 
141
- # dispatch on update event
142
134
  event = Event(Event.PLUGIN_SETTINGS_CHANGED)
143
- self.window.dispatch(event)
144
- self.window.controller.ui.update_tokens() # update tokens (if cmd syntax changed)
135
+ window.dispatch(event)
136
+ window.controller.ui.update_tokens()
145
137
 
146
138
  def close(self):
147
139
  """Close plugin settings dialog"""
@@ -157,15 +149,12 @@ class Settings:
157
149
  """
158
150
  if not force:
159
151
  self.window.ui.dialogs.confirm(
160
- type='plugin.settings.defaults.user',
152
+ type='plugin.settings.defaults.user',
161
153
  id=-1,
162
154
  msg=trans('dialog.plugin.settings.defaults.user.confirm'),
163
155
  )
164
156
  return
165
-
166
- # reload settings window
167
157
  self.init()
168
- # self.window.ui.dialogs.alert(trans('dialog.plugin.settings.defaults.user.result'))
169
158
 
170
159
  def load_defaults_app(self, force: bool = False):
171
160
  """
@@ -180,11 +169,7 @@ class Settings:
180
169
  msg=trans('dialog.plugin.settings.defaults.app.confirm'),
181
170
  )
182
171
  return
183
-
184
- # restore default options
185
172
  self.window.core.plugins.restore_options(self.current_plugin)
186
-
187
- # reload settings window
188
173
  self.init()
189
174
  self.window.ui.dialogs.alert(trans('dialog.plugin.settings.defaults.app.result'))
190
175
 
@@ -196,4 +181,4 @@ class Settings:
196
181
  :param key: option key
197
182
  :return: option value
198
183
  """
199
- return self.window.core.plugins.plugins[id].options[key]
184
+ return self.window.core.plugins.plugins[id].options[key]
@@ -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.14 03:00:00 #
9
+ # Updated Date: 2025.08.14 13:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
@@ -38,6 +38,14 @@ from pygpt_net.utils import trans
38
38
  from .experts import Experts
39
39
 
40
40
  class Editor:
41
+
42
+ TAB_IDX = {
43
+ "general": 0,
44
+ "personalize": 1,
45
+ "experts": 2,
46
+ "remote_tools": 3,
47
+ }
48
+
41
49
  def __init__(self, window=None):
42
50
  """
43
51
  Presets editor controller
@@ -77,7 +85,7 @@ class Editor:
77
85
  "label": "preset.user_name",
78
86
  },
79
87
  "description": {
80
- "type": "textarea",
88
+ "type": "text",
81
89
  "label": "preset.description",
82
90
  "placeholder": "preset.description.desc",
83
91
  },
@@ -189,25 +197,6 @@ class Editor:
189
197
  },
190
198
  }
191
199
  },
192
- "assistant_id": {
193
- "type": "text",
194
- "label": "preset.assistant_id",
195
- "description": "preset.assistant_id.desc",
196
- },
197
- "tool.function": {
198
- "type": "dict",
199
- "label": "preset.tool.function",
200
- "keys": {
201
- 'name': 'text',
202
- 'params': 'textarea',
203
- 'desc': 'textarea',
204
- },
205
- "extra": {
206
- "urls": {
207
- "Help": "https://platform.openai.com/docs/guides/function-calling",
208
- },
209
- },
210
- },
211
200
  }
212
201
  self.hidden_by_mode = { # hidden fields by mode
213
202
  MODE_CHAT: ["idx"],
@@ -268,15 +257,6 @@ class Editor:
268
257
  self.window.ui.add_hook("update.preset.agent_provider", self.hook_update)
269
258
  self.window.ui.add_hook("update.preset.agent_provider_openai", self.hook_update)
270
259
 
271
- # register functions dictionary
272
- parent = "preset"
273
- key = "tool.function"
274
- self.window.ui.dialogs.register_dictionary(
275
- key,
276
- parent,
277
- self.get_option(key),
278
- )
279
-
280
260
  def toggle_extra_options(self):
281
261
  """
282
262
  Toggle extra options in preset editor
@@ -594,6 +574,7 @@ class Editor:
594
574
  checkboxLayout.addLayout(opt_layout)
595
575
  else:
596
576
  layout.addLayout(opt_layout)
577
+ layout.addStretch(1)
597
578
  layout.addLayout(checkboxLayout)
598
579
 
599
580
  # as tab
@@ -777,6 +758,7 @@ class Editor:
777
758
  # load extra options
778
759
  self.load_extra_options(data)
779
760
 
761
+ # toggle extra options
780
762
  self.toggle_extra_options()
781
763
 
782
764
  # update experts list, after ID loaded
@@ -785,24 +767,6 @@ class Editor:
785
767
  # setup avatar config
786
768
  self.update_avatar_config(data)
787
769
 
788
- # restore functions
789
- if data.has_functions():
790
- functions = data.get_functions()
791
- values = []
792
- for function in functions:
793
- values.append(
794
- {
795
- "name": function['name'],
796
- "params": function['params'],
797
- "desc": function['desc'],
798
- }
799
- )
800
- self.window.ui.config[self.id]['tool.function'].items = values
801
- self.window.ui.config[self.id]['tool.function'].model.updateData(values)
802
- else:
803
- self.window.ui.config[self.id]['tool.function'].items = []
804
- self.window.ui.config[self.id]['tool.function'].model.updateData([])
805
-
806
770
  # set focus to name field
807
771
  current_model = self.window.core.config.get('model')
808
772
  # set current model in combo box as selected
@@ -954,16 +918,9 @@ class Editor:
954
918
  # sort by name
955
919
  self.window.core.presets.sort_by_name()
956
920
 
957
- # switch to editing preset, if new
958
- if is_new:
959
- self.window.controller.presets.set(mode, id)
960
- self.window.controller.presets.select_model()
961
- else:
962
- # switch to model if current preset
963
- current_preset = self.window.core.config.get('preset')
964
- if current_preset is not None and current_preset == id:
965
- self.window.controller.presets.set(mode, current_preset)
966
- self.window.controller.presets.select_model()
921
+ # switch to editing preset on save
922
+ self.window.controller.presets.set(mode, id)
923
+ self.window.controller.presets.select_model()
967
924
 
968
925
  # update presets list
969
926
  no_scroll = False
@@ -1000,36 +957,6 @@ class Editor:
1000
957
  'function': [], # functions are assigned separately (below)
1001
958
  }
1002
959
 
1003
- # assign functions tool
1004
- values = self.window.controller.config.get_value(
1005
- parent_id=self.id,
1006
- key='tool.function',
1007
- option=self.options['tool.function'],
1008
- )
1009
- functions = []
1010
- for function in values:
1011
- name = function['name']
1012
- params = function['params']
1013
- desc = function['desc']
1014
- if name is None or name == "":
1015
- continue
1016
- if params is None or params == "":
1017
- params = '{"type": "object", "properties": {}}' # default empty JSON params
1018
- if desc is None:
1019
- desc = ""
1020
- functions.append(
1021
- {
1022
- "name": name,
1023
- "params": params,
1024
- "desc": desc,
1025
- }
1026
- )
1027
-
1028
- if len(functions) > 0:
1029
- preset.tools['function'] = functions
1030
- else:
1031
- preset.tools['function'] = []
1032
-
1033
960
  # extra options
1034
961
  self.append_extra_options(preset)
1035
962
 
@@ -1189,3 +1116,21 @@ class Editor:
1189
1116
  option=self.options["ai_avatar"],
1190
1117
  value="",
1191
1118
  )
1119
+
1120
+ def toggle_tab(self, name: str, show: bool = True):
1121
+ """
1122
+ Show experts tab
1123
+
1124
+ :param name: name of the tab
1125
+ :param show: Show or hide experts tab
1126
+ """
1127
+ tabs = self.window.ui.tabs['preset.editor.tabs']
1128
+ idx = self.TAB_IDX[name]
1129
+ if tabs is not None:
1130
+ if show:
1131
+ tabs.setTabEnabled(idx, True)
1132
+ tabs.setTabVisible(idx, True)
1133
+ self.experts.update_tab()
1134
+ else:
1135
+ tabs.setTabEnabled(idx, False)
1136
+ tabs.setTabVisible(idx, False)
@@ -6,12 +6,14 @@
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.01 03:00:00 #
9
+ # Updated Date: 2025.08.14 13:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.types import (
13
13
  MODE_EXPERT,
14
14
  )
15
+ from pygpt_net.utils import trans
16
+
15
17
 
16
18
  class Experts:
17
19
  def __init__(self, window=None):
@@ -74,6 +76,8 @@ class Experts:
74
76
  # clear selected list if no agent is selected
75
77
  self.window.ui.nodes['preset.editor.experts'].update_selected({})
76
78
 
79
+ self.update_tab()
80
+
77
81
  def change_available(self):
78
82
  """Change selected expert"""
79
83
  pass
@@ -157,3 +161,18 @@ class Experts:
157
161
  self.window.core.presets.remove_expert(agent_uuid, expert_uuid)
158
162
  self.update_list()
159
163
 
164
+ def update_tab(self):
165
+ """Update experts tab label with number of experts"""
166
+ num = 0
167
+ agent_uuid = self.get_current_agent_id()
168
+ if agent_uuid:
169
+ agent = self.window.core.presets.get_by_uuid(agent_uuid)
170
+ if agent:
171
+ num = len(agent.experts)
172
+ tabs = self.window.ui.tabs['preset.editor.tabs']
173
+ idx = self.window.controller.presets.editor.TAB_IDX["experts"]
174
+ if num == 0:
175
+ tabs.setTabText(idx, trans("preset.tab.experts"))
176
+ else:
177
+ tabs.setTabText(idx, trans("preset.tab.experts") + f" ({num})")
178
+