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,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.07.26 18:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import List, Dict, Any
@@ -34,6 +34,17 @@ class Plugins:
34
34
  self.settings = Settings(window)
35
35
  self.presets = Presets(window)
36
36
  self.enabled = {}
37
+ self._suspend_updates = 0
38
+
39
+ def _begin_batch(self):
40
+ self._suspend_updates += 1
41
+
42
+ def _end_batch(self):
43
+ if self._suspend_updates > 0:
44
+ self._suspend_updates -= 1
45
+ if self._suspend_updates == 0:
46
+ self.update_info()
47
+ self.update()
37
48
 
38
49
  def setup(self):
39
50
  """Set up plugins"""
@@ -41,12 +52,12 @@ class Plugins:
41
52
  self.setup_ui()
42
53
 
43
54
  try:
44
- self.window.core.plugins.clean_presets() # clean presets (remove invalid keys)
55
+ self.window.core.plugins.clean_presets()
45
56
  except Exception as e:
46
57
  self.window.core.debug.error(e)
47
58
 
48
- self.presets.preset_to_current() # load from presets
49
- self.reconfigure(silent=True) # load plugins settings
59
+ self.presets.preset_to_current()
60
+ self.reconfigure(silent=True)
50
61
 
51
62
  def reconfigure(self, silent: bool = False):
52
63
  """
@@ -60,31 +71,30 @@ class Plugins:
60
71
 
61
72
  def setup_ui(self):
62
73
  """Set up plugins UI"""
63
- for id in self.window.core.plugins.get_ids():
64
- try:
65
- # setup UI
66
- self.window.core.plugins.get(id).setup_ui()
67
- except AttributeError:
68
- pass
69
-
70
- # show/hide UI elements
71
- self.handle_types()
74
+ pm = self.window.core.plugins
75
+ for pid in pm.get_ids():
76
+ plugin = pm.get(pid)
77
+ fn = getattr(plugin, "setup_ui", None)
78
+ if callable(fn):
79
+ fn()
72
80
 
73
- # tmp dump locales
74
- # self.window.core.plugins.dump_locales()
81
+ self.handle_types()
75
82
 
76
83
  def setup_menu(self):
77
84
  """Set up plugins menu"""
78
- for id in self.window.core.plugins.get_ids():
79
- if id in self.window.ui.menu['plugins']:
85
+ pm = self.window.core.plugins
86
+ ui_menu = self.window.ui.menu
87
+ menu_plugins = ui_menu['plugins']
88
+ for pid in pm.get_ids():
89
+ if pid in menu_plugins:
80
90
  continue
81
- name = self.window.core.plugins.get_name(id)
82
- tooltip = self.window.core.plugins.get_desc(id)
83
- self.window.ui.menu['plugins'][id] = QAction(name, self.window, checkable=True)
84
- self.window.ui.menu['plugins'][id].triggered.connect(
85
- lambda checked=None, id=id: self.toggle(id))
86
- self.window.ui.menu['plugins'][id].setToolTip(tooltip)
87
- self.window.ui.menu['menu.plugins'].addAction(self.window.ui.menu['plugins'][id])
91
+ name = pm.get_name(pid)
92
+ tooltip = pm.get_desc(pid)
93
+ act = QAction(name, self.window, checkable=True)
94
+ act.triggered.connect(lambda checked=None, id=pid: self.toggle(id))
95
+ act.setToolTip(tooltip)
96
+ menu_plugins[pid] = act
97
+ ui_menu['menu.plugins'].addAction(act)
88
98
 
89
99
  def setup_config(self, silent: bool = False):
90
100
  """
@@ -92,27 +102,31 @@ class Plugins:
92
102
 
93
103
  :param silent: silent mode
94
104
  """
95
- for id in self.window.core.plugins.get_ids():
96
- if id in self.window.core.config.get('plugins_enabled'):
97
- if self.window.core.config.data['plugins_enabled'][id]:
98
- self.enable(id)
105
+ pm = self.window.core.plugins
106
+ cfg = self.window.core.config
107
+ cfg_enabled = cfg.get('plugins_enabled')
108
+ self._begin_batch()
109
+ try:
110
+ for pid in pm.get_ids():
111
+ if pid in cfg_enabled:
112
+ if cfg.data['plugins_enabled'][pid]:
113
+ self.enable(pid)
114
+ else:
115
+ self.disable(pid, silent=silent)
99
116
  else:
100
- self.disable(id, silent=silent)
101
- else:
102
- self.disable(id, silent=silent)
117
+ self.disable(pid, silent=silent)
118
+ finally:
119
+ self._end_batch()
103
120
 
104
121
  def update(self):
105
122
  """Update plugins menu"""
106
- for id in self.window.ui.menu['plugins']:
107
- self.window.ui.menu['plugins'][id].setChecked(False)
108
-
109
- for id in self.enabled:
110
- if self.enabled[id]:
111
- self.window.ui.menu['plugins'][id].setChecked(True)
123
+ menu_plugins = self.window.ui.menu['plugins']
124
+ for pid, action in menu_plugins.items():
125
+ action.setChecked(self.enabled.get(pid, False))
112
126
 
113
127
  self.handle_types()
114
- self.window.controller.ui.mode.update() # refresh active elements
115
- self.window.controller.ui.vision.update() # vision camera
128
+ self.window.controller.ui.mode.update()
129
+ self.window.controller.ui.vision.update()
116
130
 
117
131
  def enable(self, id: str):
118
132
  """
@@ -120,22 +134,24 @@ class Plugins:
120
134
 
121
135
  :param id: plugin id
122
136
  """
123
- if self.window.core.plugins.is_registered(id):
124
- self.enabled[id] = True
125
- self.window.core.plugins.enable(id)
137
+ pm = self.window.core.plugins
138
+ if not pm.is_registered(id):
139
+ return
140
+ if self.enabled.get(id, False):
141
+ return
126
142
 
127
- # dispatch plugin enable event
128
- event = Event(Event.ENABLE, {
129
- 'value': id,
130
- })
131
- self.window.dispatch(event)
143
+ self.enabled[id] = True
144
+ pm.enable(id)
132
145
 
133
- # update audio menu
134
- if self.has_type(id, 'audio.input') or self.has_type(id, 'audio.output'):
135
- self.window.controller.audio.update()
146
+ event = Event(Event.ENABLE, {'value': id})
147
+ self.window.dispatch(event)
136
148
 
137
- self.update_info()
138
- self.update()
149
+ if self.has_type(id, 'audio.input') or self.has_type(id, 'audio.output'):
150
+ self.window.controller.audio.update()
151
+
152
+ if self._suspend_updates == 0:
153
+ self.update_info()
154
+ self.update()
139
155
 
140
156
  def disable(self, id: str, silent: bool = False):
141
157
  """
@@ -144,25 +160,26 @@ class Plugins:
144
160
  :param id: plugin id
145
161
  :param silent: silent mode
146
162
  """
147
- if self.window.core.plugins.is_registered(id):
148
- self.enabled[id] = False
149
- self.window.core.plugins.disable(id)
163
+ pm = self.window.core.plugins
164
+ if not pm.is_registered(id):
165
+ return
166
+ if not self.enabled.get(id, False):
167
+ return
150
168
 
151
- if not silent:
152
- # dispatch plugin disable event
153
- event = Event(Event.DISABLE, {
154
- 'value': id,
155
- })
156
- self.window.dispatch(event, all=True) # dispatch to all plugins, including disabled now
169
+ self.enabled[id] = False
170
+ pm.disable(id)
157
171
 
158
- # update audio menu
159
- if self.has_type(id, 'audio.input') or self.has_type(id, 'audio.output'):
160
- self.window.controller.audio.update()
172
+ if not silent:
173
+ event = Event(Event.DISABLE, {'value': id})
174
+ self.window.dispatch(event, all=True)
175
+ if self.has_type(id, 'audio.input') or self.has_type(id, 'audio.output'):
176
+ self.window.controller.audio.update()
161
177
 
162
- self.update_info()
163
- self.update()
178
+ if self._suspend_updates == 0:
179
+ self.update_info()
180
+ self.update()
164
181
 
165
- def is_enabled(self, id: str):
182
+ def is_enabled(self, id: str) -> bool:
166
183
  """
167
184
  Check if plugin is enabled
168
185
 
@@ -170,10 +187,7 @@ class Plugins:
170
187
  :return: True if enabled
171
188
  :rtype: bool
172
189
  """
173
- if self.window.core.plugins.is_registered(id):
174
- if id in self.enabled:
175
- return self.enabled[id]
176
- return False
190
+ return self.window.core.plugins.is_registered(id) and self.enabled.get(id, False)
177
191
 
178
192
  def toggle(self, id: str):
179
193
  """
@@ -187,12 +201,9 @@ class Plugins:
187
201
  else:
188
202
  self.enable(id)
189
203
 
190
- self.handle_types()
191
- self.window.controller.ui.update_tokens() # refresh tokens
192
- self.window.controller.ui.mode.update() # refresh active elements
193
- self.window.controller.ui.vision.update() # vision camera
194
- self.window.controller.attachment.update() # attachments update
195
- self.presets.save_current() # save settings in current preset
204
+ self.window.controller.ui.update_tokens()
205
+ self.window.controller.attachment.update()
206
+ self.presets.save_current()
196
207
 
197
208
  def set_by_tab(self, idx: int):
198
209
  """
@@ -200,13 +211,14 @@ class Plugins:
200
211
 
201
212
  :param idx: tab index
202
213
  """
214
+ pm = self.window.core.plugins
203
215
  plugin_idx = 0
204
- for id in self.window.core.plugins.get_ids():
205
- if self.window.core.plugins.has_options(id):
216
+ for pid in pm.get_ids():
217
+ if pm.has_options(pid):
206
218
  if plugin_idx == idx:
207
- self.settings.current_plugin = id
219
+ self.settings.current_plugin = pid
208
220
  break
209
- plugin_idx += 1
221
+ plugin_idx += 1
210
222
  current = self.window.ui.models['plugin.list'].index(idx, 0)
211
223
  self.window.ui.nodes['plugin.list'].setCurrentIndex(current)
212
224
 
@@ -217,14 +229,11 @@ class Plugins:
217
229
  :param plugin_id: plugin id
218
230
  :return: tab index
219
231
  """
220
- plugin_idx = None
221
- i = 0
222
- for id in self.window.core.plugins.get_ids():
223
- if id == plugin_id:
224
- plugin_idx = i
225
- break
226
- i += 1
227
- return plugin_idx
232
+ pm = self.window.core.plugins
233
+ for i, pid in enumerate(pm.get_ids()):
234
+ if pid == plugin_id:
235
+ return i
236
+ return None
228
237
 
229
238
  def unregister(self, id: str):
230
239
  """
@@ -233,22 +242,22 @@ class Plugins:
233
242
  :param id: plugin id
234
243
  """
235
244
  self.window.core.plugins.unregister(id)
236
- if id in self.enabled:
237
- self.enabled.pop(id)
245
+ self.enabled.pop(id, None)
238
246
 
239
247
  def destroy(self):
240
248
  """Destroy plugins workers"""
241
-
242
- # send force stop event
243
249
  event = Event(Event.FORCE_STOP, {})
244
250
  self.window.dispatch(event)
245
251
 
246
- for id in self.window.core.plugins.get_ids():
247
- try:
248
- # destroy plugin workers
249
- self.window.core.plugins.destroy(id)
250
- except AttributeError:
251
- pass
252
+ pm = self.window.core.plugins
253
+ for pid in pm.get_ids():
254
+ plugin = pm.get(pid)
255
+ fn = getattr(pm, "destroy", None)
256
+ if callable(fn):
257
+ try:
258
+ pm.destroy(pid)
259
+ except AttributeError:
260
+ pass
252
261
 
253
262
  def has_type(self, id: str, type: str):
254
263
  """
@@ -257,10 +266,10 @@ class Plugins:
257
266
  :param type: type to check
258
267
  :return: True if has type
259
268
  """
260
- if self.window.core.plugins.is_registered(id):
261
- if type in self.window.core.plugins.get(id).type:
262
- return True
263
- return False
269
+ pm = self.window.core.plugins
270
+ if not pm.is_registered(id):
271
+ return False
272
+ return type in pm.get(id).type
264
273
 
265
274
  def is_type_enabled(self, type: str) -> bool:
266
275
  """
@@ -269,39 +278,25 @@ class Plugins:
269
278
  :param type: plugin type
270
279
  :return: True if enabled
271
280
  """
272
- enabled = False
273
- for id in self.window.core.plugins.get_ids():
274
- if type in self.window.core.plugins.get(id).type and self.is_enabled(id):
275
- enabled = True
276
- break
277
- return enabled
281
+ pm = self.window.core.plugins
282
+ return any((type in pm.get(pid).type) and self.is_enabled(pid) for pid in pm.get_ids())
278
283
 
279
284
  def handle_types(self):
280
285
  """Handle plugin type"""
281
- for type in self.window.core.plugins.allowed_types:
282
-
283
- enabled = self.is_type_enabled(type)
284
-
285
- if type == 'audio.input':
286
+ pm = self.window.core.plugins
287
+ for t in pm.allowed_types:
288
+ enabled = self.is_type_enabled(t)
289
+ if t == 'audio.input':
286
290
  self.window.controller.audio.handle_audio_input(enabled)
287
-
288
- elif type == 'audio.output':
291
+ elif t == 'audio.output':
289
292
  self.window.controller.audio.handle_audio_output(enabled)
290
-
291
- elif type == 'schedule':
293
+ elif t == 'schedule':
292
294
  if enabled:
293
295
  self.window.ui.plugin_addon['schedule'].setVisible(True)
294
- # get tasks count by throwing "get option" event
295
- num = 0
296
- data = {
297
- 'name': 'scheduled_tasks_count',
298
- 'value': num,
299
- }
296
+ data = {'name': 'scheduled_tasks_count', 'value': 0}
300
297
  event = Event(Event.PLUGIN_OPTION_GET, data)
301
298
  self.window.dispatch(event)
302
- if 'value' in event.data:
303
- num = event.data['value']
304
- # update tray menu
299
+ num = event.data.get('value', 0)
305
300
  self.window.ui.tray.update_schedule_tasks(num)
306
301
  else:
307
302
  self.window.ui.plugin_addon['schedule'].setVisible(False)
@@ -309,58 +304,70 @@ class Plugins:
309
304
 
310
305
  def on_update(self):
311
306
  """Called on update"""
312
- for id in self.window.core.plugins.get_ids():
313
- if self.is_enabled(id):
314
- try:
315
- self.window.core.plugins.get(id).on_update()
316
- except AttributeError:
317
- pass
307
+ pm = self.window.core.plugins
308
+ for pid in pm.get_ids():
309
+ if self.is_enabled(pid):
310
+ fn = getattr(pm.get(pid), "on_update", None)
311
+ if callable(fn):
312
+ fn()
318
313
 
319
314
  def on_post_update(self):
320
315
  """Called on post update"""
321
- for id in self.window.core.plugins.get_ids():
322
- if self.is_enabled(id):
323
- try:
324
- self.window.core.plugins.get(id).on_post_update()
325
- except AttributeError:
326
- pass
316
+ pm = self.window.core.plugins
317
+ for pid in pm.get_ids():
318
+ if self.is_enabled(pid):
319
+ fn = getattr(pm.get(pid), "on_post_update", None)
320
+ if callable(fn):
321
+ fn()
327
322
 
328
323
  def update_info(self):
329
324
  """Update plugins info"""
330
- enabled_list = []
331
- for id in self.window.core.plugins.get_ids():
332
- if self.is_enabled(id):
333
- enabled_list.append(self.window.core.plugins.get(id).name)
334
- tooltip = " + ".join(enabled_list)
335
-
336
- count_str = ""
325
+ pm = self.window.core.plugins
326
+ enabled_names = []
337
327
  c = 0
338
- if len(self.window.core.plugins.get_ids()) > 0:
339
- for id in self.window.core.plugins.get_ids():
340
- if self.is_enabled(id):
341
- c += 1
328
+ for pid in pm.get_ids():
329
+ if self.is_enabled(pid):
330
+ c += 1
331
+ enabled_names.append(pm.get(pid).name)
342
332
 
343
- if c > 0:
344
- count_str = "+ " + str(c) + " " + trans('chatbox.plugins')
333
+ tooltip = " + ".join(enabled_names)
334
+ count_str = f"+ {c} {trans('chatbox.plugins')}" if c > 0 else ""
345
335
  self.window.ui.nodes['chat.plugins'].setText(count_str)
346
336
  self.window.ui.nodes['chat.plugins'].setToolTip(tooltip)
347
337
 
348
- def apply_cmds_all(
338
+ def _apply_cmds_common(
349
339
  self,
340
+ event_type: str,
350
341
  ctx: CtxItem,
351
- cmds: List[Dict[str, Any]]
342
+ cmds: List[Dict[str, Any]],
343
+ all: bool = False,
344
+ execute_only: bool = False
352
345
  ):
353
- """
354
- Apply all commands (inline or not)
346
+ commands = self.window.core.command.from_commands(cmds)
347
+ if len(commands) == 0:
348
+ return
355
349
 
356
- :param ctx: context
357
- :param cmds: commands
358
- :return: results
359
- """
360
- if self.window.core.config.get("cmd"):
361
- return self.apply_cmds(ctx, cmds)
350
+ event = Event(event_type, {'commands': commands})
351
+ mode = self.window.core.config.get('mode')
352
+ self.log("Executing plugin commands..." if event_type == Event.CMD_EXECUTE else "Executing inline plugin commands...")
353
+ change_status = True
354
+ if mode == MODE_AGENT and len(cmds) == 1 and cmds[0].get("cmd") == "goal_update":
355
+ change_status = False
356
+ wait_str = trans('status.cmd.wait')
357
+ if change_status:
358
+ self.window.update_status(wait_str)
359
+
360
+ ctx.results = []
361
+ event.ctx = ctx
362
+ if event_type == Event.CMD_EXECUTE:
363
+ self.window.controller.command.dispatch(event, all=all, execute_only=execute_only)
362
364
  else:
363
- return self.apply_cmds_inline(ctx, cmds)
365
+ self.window.controller.command.dispatch(event)
366
+
367
+ current = self.window.ui.get_status()
368
+ if current == wait_str:
369
+ self.window.update_status("")
370
+ return ctx.results
364
371
 
365
372
  def apply_cmds(
366
373
  self,
@@ -377,38 +384,25 @@ class Plugins:
377
384
  :param all: True to apply all commands, False to apply only enabled commands
378
385
  :param execute_only: True to execute commands only, without any additional event
379
386
  """
380
- commands = self.window.core.command.from_commands(cmds)
381
- if len(commands) == 0:
382
- return
383
-
384
- # dispatch command execute event
385
- event = Event(Event.CMD_EXECUTE, {
386
- 'commands': commands,
387
- })
387
+ return self._apply_cmds_common(Event.CMD_EXECUTE, ctx, cmds, all=all, execute_only=execute_only)
388
388
 
389
- # don't change status if only goal update command
390
- self.log("Executing plugin commands...")
391
- mode = self.window.core.config.get('mode')
392
- change_status = True
393
- if mode == MODE_AGENT:
394
- if len(cmds) == 1 and cmds[0]["cmd"] == "goal_update":
395
- change_status = False
396
- if change_status:
397
- self.window.update_status(trans('status.cmd.wait'))
389
+ def apply_cmds_all(
390
+ self,
391
+ ctx: CtxItem,
392
+ cmds: List[Dict[str, Any]]
393
+ ):
394
+ """
395
+ Apply all commands (inline or not)
398
396
 
399
- ctx.results = []
400
- event.ctx = ctx
401
- self.window.controller.command.dispatch(
402
- event,
403
- all=all,
404
- execute_only=execute_only,
405
- )
406
- # reset status if nothing executed
407
- current = self.window.ui.get_status()
408
- if current == trans('status.cmd.wait'):
409
- self.window.update_status("")
397
+ :param ctx: context
398
+ :param cmds: commands
399
+ :return: results
400
+ """
401
+ if self.window.core.config.get("cmd"):
402
+ return self.apply_cmds(ctx, cmds)
403
+ else:
404
+ return self.apply_cmds_inline(ctx, cmds)
410
405
 
411
- return ctx.results
412
406
 
413
407
  def apply_cmds_inline(
414
408
  self,
@@ -421,47 +415,39 @@ class Plugins:
421
415
  :param ctx: CtxItem
422
416
  :param cmds: commands list
423
417
  """
424
- commands = self.window.core.command.from_commands(cmds)
425
- if len(commands) == 0:
426
- return
427
-
428
- # dispatch inline command event
429
- event = Event(Event.CMD_INLINE, {
430
- 'commands': commands,
431
- })
432
-
433
- # don't change status if only goal update command
434
- self.log("Executing inline plugin commands...")
435
- mode = self.window.core.config.get('mode')
436
- change_status = True
437
- if mode == MODE_AGENT:
438
- if len(cmds) == 1 and cmds[0]["cmd"] == "goal_update":
439
- change_status = False
440
- if change_status:
441
- self.window.update_status(trans('status.cmd.wait'))
442
-
443
- ctx.results = []
444
- event.ctx = ctx
445
- self.window.controller.command.dispatch(event)
446
-
447
- # reset status if nothing executed
448
- current = self.window.ui.get_status()
449
- if current == trans('status.cmd.wait'):
450
- self.window.update_status("")
451
-
452
- return ctx.results
418
+ return self._apply_cmds_common(Event.CMD_INLINE, ctx, cmds)
453
419
 
454
420
  def reload(self):
455
421
  """Reload plugins"""
456
- self.window.core.plugins.reload_all() # reload all plugin options
422
+ self.window.core.plugins.reload_all()
457
423
  self.setup()
458
424
  self.settings.setup()
459
425
  self.update()
460
426
 
427
+ def save_all(self):
428
+ """Save plugin settings"""
429
+ pm = self.window.core.plugins
430
+ cfg_plugins = self.window.core.config.data['plugins']
431
+
432
+ for pid, plugin in pm.plugins.items():
433
+ plugin.setup()
434
+ if pid not in cfg_plugins:
435
+ cfg_plugins[pid] = {}
436
+ dest = cfg_plugins[pid]
437
+ for key, opt in plugin.options.items():
438
+ dest[key] = opt['value']
439
+
440
+ for key in list(cfg_plugins.keys()):
441
+ if key not in pm.plugins:
442
+ cfg_plugins.pop(key)
443
+
444
+ self.window.controller.plugins.presets.save_current()
445
+ self.window.core.config.save()
446
+
461
447
  def log(self, data: Any):
462
448
  """
463
449
  Log data to debug
464
450
 
465
451
  :param data: Data to log
466
452
  """
467
- self.window.core.debug.info(data)
453
+ self.window.core.debug.info(data)