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.08.14 01:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -29,6 +29,10 @@ from pygpt_net.item.preset import PresetItem
29
29
  from pygpt_net.utils import trans
30
30
 
31
31
 
32
+ _FILENAME_SANITIZE_RE = re.compile(r'[^a-zA-Z0-9_\-\.]')
33
+ _VALIDATE_FILENAME_RE = re.compile(r'[^\w\s\-\.]')
34
+
35
+
32
36
  class Presets:
33
37
  def __init__(self, window=None):
34
38
  """
@@ -55,18 +59,20 @@ class Presets:
55
59
 
56
60
  :return: True if bot
57
61
  """
58
- mode = self.window.core.config.get('mode')
59
- if mode != MODE_AGENT_OPENAI:
62
+ w = self.window
63
+ cfg = w.core.config
64
+ if cfg.get('mode') != MODE_AGENT_OPENAI:
60
65
  return False
61
- preset = self.window.core.config.get('preset')
62
- if preset and preset != "*":
63
- preset_data = self.window.core.presets.get_by_id(mode, preset)
64
- if preset_data:
65
- if preset_data.agent_openai:
66
- if (preset_data.agent_provider_openai
67
- and preset_data.agent_provider_openai.startswith("openai_agent_bot")):
68
- return True
69
- return False
66
+ preset_id = cfg.get('preset')
67
+ if not preset_id or preset_id == "*":
68
+ return False
69
+ preset_data = w.core.presets.get_by_id(MODE_AGENT_OPENAI, preset_id)
70
+ return bool(
71
+ preset_data
72
+ and preset_data.agent_openai
73
+ and preset_data.agent_provider_openai
74
+ and preset_data.agent_provider_openai.startswith("openai_agent_bot")
75
+ )
70
76
 
71
77
  def select(self, idx: int):
72
78
  """
@@ -74,21 +80,19 @@ class Presets:
74
80
 
75
81
  :param idx: value of the list (row idx)
76
82
  """
77
- # check if preset change is not locked
78
83
  if self.preset_change_locked():
79
84
  return
80
- mode = self.window.core.config.get('mode')
81
- self.set_by_idx(mode, idx) # set global and current dict
82
- preset_id = self.window.core.config.get('preset')
83
-
84
- # update all layout
85
- self.window.controller.ui.update()
86
- self.window.controller.model.select_current()
87
- self.window.dispatch(AppEvent(AppEvent.PRESET_SELECTED)) # app event
85
+ w = self.window
86
+ mode = w.core.config.get('mode')
87
+ self.set_by_idx(mode, idx)
88
+ preset_id = w.core.config.get('preset')
89
+ w.controller.ui.update()
90
+ w.controller.model.select_current()
91
+ w.dispatch(AppEvent(AppEvent.PRESET_SELECTED))
88
92
  self.set_selected(idx)
89
- if self.window.controller.presets.editor.opened:
90
- if self.window.controller.presets.editor.current != preset_id:
91
- self.editor.init(preset_id)
93
+ editor_ctrl = w.controller.presets.editor
94
+ if editor_ctrl.opened and editor_ctrl.current != preset_id:
95
+ self.editor.init(preset_id)
92
96
 
93
97
  def get_current(self) -> Optional[PresetItem]:
94
98
  """
@@ -96,33 +100,39 @@ class Presets:
96
100
 
97
101
  :return: preset item
98
102
  """
99
- preset_id = self.window.core.config.get('preset')
100
- mode = self.window.core.config.get('mode')
101
- if preset_id is not None and preset_id != "":
102
- if self.window.core.presets.has(mode, preset_id):
103
- return self.window.core.presets.get_by_id(mode, preset_id)
103
+ w = self.window
104
+ cfg = w.core.config
105
+ preset_id = cfg.get('preset')
106
+ mode = cfg.get('mode')
107
+ if preset_id and w.core.presets.has(mode, preset_id):
108
+ return w.core.presets.get_by_id(mode, preset_id)
104
109
  return None
105
110
 
106
111
  def next(self):
107
112
  """Select next preset"""
108
- idx = self.window.ui.nodes['preset.presets'].currentIndex().row()
109
- idx += 1
110
- if idx >= self.window.ui.models['preset.presets'].rowCount():
111
- idx = 0
113
+ nodes = self.window.ui.nodes
114
+ models = self.window.ui.models
115
+ count = models['preset.presets'].rowCount()
116
+ if count <= 0:
117
+ return
118
+ idx = (nodes['preset.presets'].currentIndex().row() + 1) % count
112
119
  self.select(idx)
113
120
 
114
121
  def prev(self):
115
122
  """Select previous preset"""
116
- idx = self.window.ui.nodes['preset.presets'].currentIndex().row()
117
- idx -= 1
118
- if idx < 0:
119
- idx = self.window.ui.models['preset.presets'].rowCount() - 1
123
+ nodes = self.window.ui.nodes
124
+ models = self.window.ui.models
125
+ count = models['preset.presets'].rowCount()
126
+ if count <= 0:
127
+ return
128
+ idx = (nodes['preset.presets'].currentIndex().row() - 1) % count
120
129
  self.select(idx)
121
130
 
122
131
  def use(self):
123
132
  """Copy preset prompt to input"""
124
- self.window.controller.chat.common.append_to_input(
125
- self.window.ui.nodes['preset.prompt'].toPlainText()
133
+ w = self.window
134
+ w.controller.chat.common.append_to_input(
135
+ w.ui.nodes['preset.prompt'].toPlainText()
126
136
  )
127
137
 
128
138
  def paste_prompt(
@@ -136,15 +146,19 @@ class Presets:
136
146
  :param idx: prompt index
137
147
  :param parent: parent name
138
148
  """
139
- template = self.window.core.prompt.template.get_by_id(idx)
149
+ w = self.window
150
+ template = w.core.prompt.template.get_by_id(idx)
140
151
  if template is None:
141
152
  return
153
+ target = None
142
154
  if parent == "global":
143
- self.paste_to_textarea(self.window.ui.nodes['preset.prompt'], template['prompt'])
155
+ target = w.ui.nodes['preset.prompt']
144
156
  elif parent == "input":
145
- self.paste_to_textarea(self.window.ui.nodes['input'], template['prompt'])
157
+ target = w.ui.nodes['input']
146
158
  elif parent == "editor":
147
- self.paste_to_textarea(self.window.ui.config["preset"]["prompt"], template['prompt'])
159
+ target = w.ui.config["preset"]["prompt"]
160
+ if target is not None:
161
+ self.paste_to_textarea(target, template['prompt'])
148
162
 
149
163
  def paste_custom_prompt(
150
164
  self,
@@ -157,15 +171,19 @@ class Presets:
157
171
  :param idx: prompt index
158
172
  :param parent: parent name
159
173
  """
160
- template = self.window.core.prompt.custom.get_by_id(idx)
174
+ w = self.window
175
+ template = w.core.prompt.custom.get_by_id(idx)
161
176
  if template is None:
162
177
  return
178
+ target = None
163
179
  if parent == "global":
164
- self.paste_to_textarea(self.window.ui.nodes['preset.prompt'], template.content)
180
+ target = w.ui.nodes['preset.prompt']
165
181
  elif parent == "input":
166
- self.paste_to_textarea(self.window.ui.nodes['input'], template.content)
182
+ target = w.ui.nodes['input']
167
183
  elif parent == "editor":
168
- self.paste_to_textarea(self.window.ui.config["preset"]["prompt"], template.content)
184
+ target = w.ui.config["preset"]["prompt"]
185
+ if target is not None:
186
+ self.paste_to_textarea(target, template.content)
169
187
 
170
188
  def save_prompt(
171
189
  self,
@@ -178,19 +196,21 @@ class Presets:
178
196
  :param name: prompt name
179
197
  :param force: force save
180
198
  """
181
- content = self.window.ui.nodes['input'].toPlainText()
199
+ w = self.window
200
+ content = w.ui.nodes['input'].toPlainText()
182
201
  if content.strip() == "":
183
- self.window.update_status("Prompt is empty!")
202
+ w.update_status("Prompt is empty!")
184
203
  return
185
204
  if not force:
186
- self.window.ui.dialog['rename'].id = 'prompt.custom.new'
187
- self.window.ui.dialog['rename'].input.setText(content[:40] + "...")
188
- self.window.ui.dialog['rename'].current = ""
189
- self.window.ui.dialog['rename'].show()
205
+ dlg = w.ui.dialog['rename']
206
+ dlg.id = 'prompt.custom.new'
207
+ dlg.input.setText(content[:40] + "...")
208
+ dlg.current = ""
209
+ dlg.show()
190
210
  return
191
- self.window.ui.dialog['rename'].close()
192
- self.window.core.prompt.custom.new(name, content)
193
- self.window.update_status("Prompt saved")
211
+ w.ui.dialog['rename'].close()
212
+ w.core.prompt.custom.new(name, content)
213
+ w.update_status("Prompt saved")
194
214
 
195
215
  def rename_prompt(
196
216
  self,
@@ -205,19 +225,21 @@ class Presets:
205
225
  :param name: new name
206
226
  :param force: force rename
207
227
  """
208
- item = self.window.core.prompt.custom.get_by_id(uuid)
228
+ w = self.window
229
+ item = w.core.prompt.custom.get_by_id(uuid)
209
230
  if item is None:
210
231
  return
211
232
  if not force:
212
- self.window.ui.dialog['rename'].id = 'prompt.custom.rename'
213
- self.window.ui.dialog['rename'].input.setText(item.name)
214
- self.window.ui.dialog['rename'].current = uuid
215
- self.window.ui.dialog['rename'].show()
233
+ dlg = w.ui.dialog['rename']
234
+ dlg.id = 'prompt.custom.rename'
235
+ dlg.input.setText(item.name)
236
+ dlg.current = uuid
237
+ dlg.show()
216
238
  return
217
- self.window.ui.dialog['rename'].close()
239
+ w.ui.dialog['rename'].close()
218
240
  item.name = name
219
- self.window.core.prompt.custom.save()
220
- self.window.update_status("Prompt renamed")
241
+ w.core.prompt.custom.save()
242
+ w.update_status("Prompt renamed")
221
243
 
222
244
  def delete_prompt(
223
245
  self,
@@ -230,18 +252,20 @@ class Presets:
230
252
  :param uuid: prompt ID
231
253
  :param force: force delete
232
254
  """
233
- item = self.window.core.prompt.custom.get_by_id(uuid)
255
+ w = self.window
256
+ item = w.core.prompt.custom.get_by_id(uuid)
234
257
  if item is None:
235
258
  return
236
259
  if not force:
237
- self.window.ui.dialog['confirm'].type = 'prompt.custom.delete'
238
- self.window.ui.dialog['confirm'].id = uuid
239
- self.window.ui.dialog['confirm'].message = "Are you sure you want to delete this prompt?"
240
- self.window.ui.dialog['confirm'].show()
260
+ dlg = w.ui.dialog['confirm']
261
+ dlg.type = 'prompt.custom.delete'
262
+ dlg.id = uuid
263
+ dlg.message = "Are you sure you want to delete this prompt?"
264
+ dlg.show()
241
265
  return
242
- self.window.ui.dialog['confirm'].close()
243
- self.window.core.prompt.custom.delete(uuid)
244
- self.window.update_status("Prompt deleted")
266
+ w.ui.dialog['confirm'].close()
267
+ w.core.prompt.custom.delete(uuid)
268
+ w.update_status("Prompt deleted")
245
269
 
246
270
  def paste_to_textarea(
247
271
  self,
@@ -254,20 +278,16 @@ class Presets:
254
278
  :param textarea: textarea widget
255
279
  :param text: text to paste
256
280
  """
257
- separator = "\n"
258
281
  prev_text = textarea.toPlainText()
259
282
  cur = textarea.textCursor()
283
+ cur.beginEditBlock()
260
284
  cur.movePosition(QTextCursor.End)
261
- text = str(text).strip()
285
+ txt = str(text).strip()
262
286
  if prev_text.strip() != "":
263
- text = separator + text
264
- s = text
265
- while s:
266
- head, sep, s = s.partition("\n")
267
- cur.insertText(head)
268
- if sep:
269
- cur.insertBlock()
270
- cur.movePosition(QTextCursor.End)
287
+ cur.insertText("\n")
288
+ if txt:
289
+ cur.insertText(txt)
290
+ cur.endEditBlock()
271
291
  textarea.setTextCursor(cur)
272
292
  textarea.setFocus()
273
293
 
@@ -282,12 +302,13 @@ class Presets:
282
302
  :param mode: mode name
283
303
  :param preset_id: preset ID
284
304
  """
285
- if not self.window.core.presets.has(mode, preset_id):
305
+ w = self.window
306
+ if not w.core.presets.has(mode, preset_id):
286
307
  return False
287
- self.window.core.config.data['preset'] = preset_id
288
- if 'current_preset' not in self.window.core.config.data:
289
- self.window.core.config.data['current_preset'] = {}
290
- self.window.core.config.data['current_preset'][mode] = preset_id
308
+ w.core.config.data['preset'] = preset_id
309
+ if 'current_preset' not in w.core.config.data:
310
+ w.core.config.data['current_preset'] = {}
311
+ w.core.config.data['current_preset'][mode] = preset_id
291
312
 
292
313
  def set_by_idx(
293
314
  self,
@@ -300,15 +321,12 @@ class Presets:
300
321
  :param mode: mode name
301
322
  :param idx: preset index
302
323
  """
303
- preset_id = self.window.core.presets.get_by_idx(idx, mode)
304
-
305
- # set preset
306
- self.window.core.config.set("preset", preset_id)
307
- if 'current_preset' not in self.window.core.config.data:
308
- self.window.core.config.data['current_preset'] = {}
309
- self.window.core.config.data['current_preset'][mode] = preset_id
310
-
311
- # select model
324
+ w = self.window
325
+ preset_id = w.core.presets.get_by_idx(idx, mode)
326
+ w.core.config.set("preset", preset_id)
327
+ if 'current_preset' not in w.core.config.data:
328
+ w.core.config.data['current_preset'] = {}
329
+ w.core.config.data['current_preset'][mode] = preset_id
312
330
  self.select_model()
313
331
 
314
332
  def select_current(self, no_scroll: bool = False):
@@ -317,97 +335,87 @@ class Presets:
317
335
 
318
336
  :param no_scroll: do not scroll to current preset
319
337
  """
320
- mode = self.window.core.config.get('mode')
321
- preset_id = self.window.core.config.get('preset')
322
- items = self.window.core.presets.get_by_mode(mode)
323
- if preset_id in items:
324
- idx = list(items.keys()).index(preset_id)
325
- # current = self.window.ui.models['preset.presets'].index(idx, 0)
326
- if no_scroll:
327
- self.window.ui.nodes['preset.presets'].store_scroll_position()
328
- self.window.ui.nodes['preset.presets'].select_by_idx(idx)
329
- if no_scroll:
330
- self.window.ui.nodes['preset.presets'].restore_scroll_position()
331
- if self.window.controller.presets.editor.opened:
332
- if self.window.controller.presets.editor.current != preset_id:
333
- self.editor.init(preset_id)
338
+ w = self.window
339
+ cfg = w.core.config
340
+ mode = cfg.get('mode')
341
+ preset_id = cfg.get('preset')
342
+ if not preset_id:
343
+ return
344
+ idx = w.core.presets.get_idx_by_id(mode, preset_id)
345
+ if idx is None or idx < 0:
346
+ return
347
+ if no_scroll:
348
+ w.ui.nodes['preset.presets'].store_scroll_position()
349
+ w.ui.nodes['preset.presets'].select_by_idx(idx)
350
+ if no_scroll:
351
+ w.ui.nodes['preset.presets'].restore_scroll_position()
352
+ editor_ctrl = w.controller.presets.editor
353
+ if editor_ctrl.opened and editor_ctrl.current != preset_id:
354
+ self.editor.init(preset_id)
334
355
 
335
356
  def select_default(self):
336
357
  """Set default preset"""
337
- preset_id = self.window.core.config.get('preset')
338
-
339
- # if preset is not set, set default
340
- if preset_id is None or preset_id == "":
341
- mode = self.window.core.config.get('mode')
342
- # set previously selected preset
343
- current = self.window.core.config.get('current_preset') # dict of modes, preset per mode
344
- if mode in current and \
345
- current[mode] is not None and \
346
- current[mode] != "" and \
347
- current[mode] in self.window.core.presets.get_by_mode(mode):
348
- self.window.core.config.set('preset', current[mode])
358
+ w = self.window
359
+ cfg = w.core.config
360
+ preset_id = cfg.get('preset')
361
+ if not preset_id:
362
+ mode = cfg.get('mode')
363
+ current = cfg.get('current_preset')
364
+ if mode in current and current[mode] and current[mode] in w.core.presets.get_by_mode(mode):
365
+ cfg.set('preset', current[mode])
349
366
  else:
350
- # or set default preset
351
- self.window.core.config.set('preset', self.window.core.presets.get_default(mode))
352
- if self.window.controller.presets.editor.opened:
353
- if self.window.controller.presets.editor.current != preset_id:
354
- self.editor.init(preset_id)
367
+ cfg.set('preset', w.core.presets.get_default(mode))
368
+ new_id = cfg.get('preset')
369
+ editor_ctrl = w.controller.presets.editor
370
+ if editor_ctrl.opened and editor_ctrl.current != new_id:
371
+ self.editor.init(new_id)
355
372
 
356
373
  def update_data(self):
357
374
  """Update preset data"""
358
- preset_id = self.window.core.config.get('preset')
359
- if preset_id is None or preset_id == "":
360
- self.reset() # clear preset fields
361
- self.window.controller.mode.reset_current()
375
+ w = self.window
376
+ cfg = w.core.config
377
+ preset_id = cfg.get('preset')
378
+ if not preset_id:
379
+ self.reset()
380
+ w.controller.mode.reset_current()
362
381
  return
363
-
364
- if preset_id not in self.window.core.presets.items:
365
- self.window.core.config.set('preset', "") # clear preset if not found
366
- self.reset() # clear preset fields
367
- self.window.controller.mode.reset_current()
382
+ if preset_id not in w.core.presets.items:
383
+ cfg.set('preset', "")
384
+ self.reset()
385
+ w.controller.mode.reset_current()
368
386
  return
369
-
370
- # update preset fields
371
- preset = self.window.core.presets.items[preset_id]
372
- self.window.ui.nodes['preset.prompt'].setPlainText(preset.prompt)
373
- # self.window.ui.nodes['preset.ai_name'].setText(preset.ai_name)
374
- # self.window.ui.nodes['preset.user_name'].setText(preset.user_name)
375
-
376
- # update current data
377
- self.window.core.config.set('prompt', preset.prompt)
378
- self.window.core.config.set('ai_name', preset.ai_name)
379
- self.window.core.config.set('user_name', preset.user_name)
380
- self.window.core.config.set('agent.llama.provider', preset.agent_provider)
381
- self.window.core.config.set('agent.openai.provider', preset.agent_provider_openai)
382
- self.window.core.config.set('agent.llama.idx', preset.idx)
387
+ preset = w.core.presets.items[preset_id]
388
+ w.ui.nodes['preset.prompt'].setPlainText(preset.prompt)
389
+ w.core.config.set('prompt', preset.prompt)
390
+ w.core.config.set('ai_name', preset.ai_name)
391
+ w.core.config.set('user_name', preset.user_name)
392
+ w.core.config.set('agent.llama.provider', preset.agent_provider)
393
+ w.core.config.set('agent.openai.provider', preset.agent_provider_openai)
394
+ w.core.config.set('agent.llama.idx', preset.idx)
383
395
 
384
396
  def update_current(self):
385
397
  """Update current mode, model and preset"""
386
- mode = self.window.core.config.get('mode')
387
-
388
- # if preset chosen, update current config
389
- preset_id = self.window.core.config.get('preset')
390
- if preset_id is not None and preset_id != "":
391
- if preset_id in self.window.core.presets.items:
392
- preset = self.window.core.presets.items[preset_id]
393
- self.window.core.config.set('user_name', preset.user_name)
394
- self.window.core.config.set('ai_name', preset.ai_name)
395
- self.window.core.config.set('prompt', preset.prompt)
396
- self.window.core.config.set('temperature', preset.temperature)
397
- self.window.core.config.set('agent.llama.provider', preset.agent_provider)
398
- self.window.core.config.set('agent.openai.provider', preset.agent_provider_openai)
399
- self.window.core.config.set('agent.llama.idx', preset.idx)
400
- return
401
-
402
- self.window.core.config.set('user_name', None)
403
- self.window.core.config.set('ai_name', None)
404
- self.window.core.config.set('temperature', 1.0)
405
-
406
- # set default prompt if mode is chat
398
+ w = self.window
399
+ cfg = w.core.config
400
+ mode = cfg.get('mode')
401
+ preset_id = cfg.get('preset')
402
+ if preset_id and preset_id in w.core.presets.items:
403
+ preset = w.core.presets.items[preset_id]
404
+ cfg.set('user_name', preset.user_name)
405
+ cfg.set('ai_name', preset.ai_name)
406
+ cfg.set('prompt', preset.prompt)
407
+ cfg.set('temperature', preset.temperature)
408
+ cfg.set('agent.llama.provider', preset.agent_provider)
409
+ cfg.set('agent.openai.provider', preset.agent_provider_openai)
410
+ cfg.set('agent.llama.idx', preset.idx)
411
+ return
412
+ cfg.set('user_name', None)
413
+ cfg.set('ai_name', None)
414
+ cfg.set('temperature', 1.0)
407
415
  if mode == MODE_CHAT:
408
- self.window.core.config.set('prompt', self.window.core.prompt.get('default'))
416
+ cfg.set('prompt', w.core.prompt.get('default'))
409
417
  else:
410
- self.window.core.config.set('prompt', None)
418
+ cfg.set('prompt', None)
411
419
 
412
420
  def get_current_functions(self) -> Optional[List[Dict]]:
413
421
  """
@@ -415,40 +423,45 @@ class Presets:
415
423
 
416
424
  :return: list of functions
417
425
  """
418
- preset_id = self.window.core.config.get('preset')
419
- if preset_id is not None and preset_id != "":
420
- if preset_id in self.window.core.presets.items:
421
- preset = self.window.core.presets.items[preset_id]
422
- if preset.has_functions():
423
- return preset.get_functions()
426
+ w = self.window
427
+ preset_id = w.core.config.get('preset')
428
+ if preset_id and preset_id in w.core.presets.items:
429
+ preset = w.core.presets.items[preset_id]
430
+ if preset.has_functions():
431
+ return preset.get_functions()
424
432
  return None
425
433
 
426
434
  def from_global(self):
427
435
  """Update current preset from global prompt"""
428
436
  if self.locked:
429
437
  return
430
- preset_id = self.window.core.config.get('preset')
431
- if preset_id is not None and preset_id != "":
432
- if preset_id in self.window.core.presets.items:
433
- preset = self.window.core.presets.items[preset_id]
434
- preset.prompt = self.window.core.config.get('prompt')
435
- self.window.core.presets.save(preset)
438
+ w = self.window
439
+ preset_id = w.core.config.get('preset')
440
+ if preset_id and preset_id in w.core.presets.items:
441
+ preset = w.core.presets.items[preset_id]
442
+ preset.prompt = w.core.config.get('prompt')
443
+ w.core.presets.save(preset)
436
444
 
437
445
  def select_model(self):
438
446
  """Select model by current preset"""
439
- mode = self.window.core.config.get('mode')
440
- preset_id = self.window.core.config.get('preset')
441
- if preset_id is not None and preset_id != "":
442
- if preset_id in self.window.core.presets.items:
443
- preset = self.window.core.presets.items[preset_id]
444
- if preset.model is not None and preset.model != "" and preset.model != "_":
445
- if preset.model in self.window.core.models.items:
446
- if self.window.core.models.has(preset.model) \
447
- and self.window.core.models.is_allowed(preset.model, mode):
448
- self.window.core.config.set('model', preset.model)
449
- self.window.controller.model.set(mode, preset.model)
450
- self.window.controller.model.init_list()
451
- self.window.controller.model.select_current()
447
+ w = self.window
448
+ cfg = w.core.config
449
+ mode = cfg.get('mode')
450
+ preset_id = cfg.get('preset')
451
+ if not preset_id or preset_id not in w.core.presets.items:
452
+ return
453
+ preset = w.core.presets.items[preset_id]
454
+ model = preset.model
455
+ if not model or model == "_":
456
+ return
457
+ models = w.core.models
458
+ if models.has(model) and models.is_allowed(model, mode):
459
+ if cfg.get('model') == model:
460
+ return
461
+ cfg.set('model', model)
462
+ w.controller.model.set(mode, model)
463
+ w.controller.model.init_list()
464
+ w.controller.model.select_current()
452
465
 
453
466
  def refresh(self, no_scroll: bool = False):
454
467
  """
@@ -456,35 +469,32 @@ class Presets:
456
469
 
457
470
  :param no_scroll: do not scroll to current
458
471
  """
459
- mode = self.window.core.config.get('mode')
460
- if mode == MODE_ASSISTANT:
472
+ w = self.window
473
+ if w.core.config.get('mode') == MODE_ASSISTANT:
461
474
  return
462
-
463
475
  if no_scroll:
464
- self.window.ui.nodes['preset.presets'].store_scroll_position()
465
- self.select_default() # if no preset then select previous from current presets
466
- self.update_current() # apply preset data to current config
467
- self.update_data() # update config and prompt from preset data
468
- self.window.controller.mode.update_temperature()
469
- self.update_list() # update presets list only
470
- self.select_current() # select current preset on list
476
+ w.ui.nodes['preset.presets'].store_scroll_position()
477
+ self.select_default()
478
+ self.update_current()
479
+ self.update_data()
480
+ w.controller.mode.update_temperature()
481
+ self.update_list()
482
+ self.select_current()
471
483
  if no_scroll:
472
- self.window.ui.nodes['preset.presets'].restore_scroll_position()
484
+ w.ui.nodes['preset.presets'].restore_scroll_position()
473
485
  self.on_changed()
474
486
 
475
-
476
487
  def update_list(self):
477
488
  """Update presets list"""
478
- mode = self.window.core.config.get('mode')
479
- items = self.window.core.presets.get_by_mode(mode)
480
- self.window.ui.toolbox.presets.update_presets(items)
489
+ w = self.window
490
+ mode = w.core.config.get('mode')
491
+ items = w.core.presets.get_by_mode(mode)
492
+ w.ui.toolbox.presets.update_presets(items)
481
493
  self.clear_selected()
482
494
 
483
495
  def reset(self):
484
496
  """Reset preset data"""
485
497
  self.window.ui.nodes['preset.prompt'].setPlainText("")
486
- # self.window.ui.nodes['preset.ai_name'].setText("")
487
- # self.window.ui.nodes['preset.user_name'].setText("")
488
498
 
489
499
  def make_filename(self, name: str) -> str:
490
500
  """
@@ -493,9 +503,7 @@ class Presets:
493
503
  :param name: preset name
494
504
  :return: preset filename
495
505
  """
496
- filename = name.lower()
497
- filename = re.sub(r'[^a-zA-Z0-9_\-\.]', '_', filename)
498
- return filename
506
+ return _FILENAME_SANITIZE_RE.sub('_', name.lower())
499
507
 
500
508
  def duplicate(self, idx: Optional[int] = None):
501
509
  """
@@ -504,19 +512,17 @@ class Presets:
504
512
  :param idx: preset index (row index)
505
513
  """
506
514
  if idx is not None:
507
- mode = self.window.core.config.get('mode')
508
- preset = self.window.core.presets.get_by_idx(idx, mode)
509
- if preset is not None and preset != "":
510
- if preset in self.window.core.presets.items:
511
- new_id = self.window.core.presets.duplicate(preset)
512
- #self.window.core.config.set('preset', new_id)
515
+ w = self.window
516
+ mode = w.core.config.get('mode')
517
+ preset = w.core.presets.get_by_idx(idx, mode)
518
+ if preset:
519
+ if preset in w.core.presets.items:
520
+ new_id = w.core.presets.duplicate(preset)
513
521
  self.update_list()
514
522
  self.refresh(no_scroll=True)
515
- idx = self.window.core.presets.get_idx_by_id(mode, new_id)
523
+ idx = w.core.presets.get_idx_by_id(mode, new_id)
516
524
  self.editor.edit(idx)
517
- self.window.update_status(trans('status.preset.duplicated'))
518
-
519
-
525
+ w.update_status(trans('status.preset.duplicated'))
520
526
 
521
527
  def enable(self, idx: Optional[int] = None):
522
528
  """
@@ -525,12 +531,12 @@ class Presets:
525
531
  :param idx: preset index (row index)
526
532
  """
527
533
  if idx is not None:
528
- mode = self.window.core.config.get('mode')
529
- preset_id = self.window.core.presets.get_by_idx(idx, mode)
530
- if preset_id is not None and preset_id != "":
531
- if preset_id in self.window.core.presets.items:
532
- self.window.core.presets.enable(preset_id)
533
- QTimer.singleShot(100, self.refresh) # delay refresh
534
+ w = self.window
535
+ mode = w.core.config.get('mode')
536
+ preset_id = w.core.presets.get_by_idx(idx, mode)
537
+ if preset_id and preset_id in w.core.presets.items:
538
+ w.core.presets.enable(preset_id)
539
+ QTimer.singleShot(100, self.refresh)
534
540
 
535
541
  def disable(self, idx: Optional[int] = None):
536
542
  """
@@ -539,12 +545,12 @@ class Presets:
539
545
  :param idx: preset index (row index)
540
546
  """
541
547
  if idx is not None:
542
- mode = self.window.core.config.get('mode')
543
- preset_id = self.window.core.presets.get_by_idx(idx, mode)
544
- if preset_id is not None and preset_id != "":
545
- if preset_id in self.window.core.presets.items:
546
- self.window.core.presets.disable(preset_id)
547
- QTimer.singleShot(100, self.refresh) # delay refresh
548
+ w = self.window
549
+ mode = w.core.config.get('mode')
550
+ preset_id = w.core.presets.get_by_idx(idx, mode)
551
+ if preset_id and preset_id in w.core.presets.items:
552
+ w.core.presets.disable(preset_id)
553
+ QTimer.singleShot(100, self.refresh)
548
554
 
549
555
  def clear(self, force: bool = False):
550
556
  """
@@ -552,35 +558,30 @@ class Presets:
552
558
 
553
559
  :param force: force clear data
554
560
  """
555
- preset = self.window.core.config.get('preset')
556
-
561
+ w = self.window
562
+ preset = w.core.config.get('preset')
557
563
  if not force:
558
- self.window.ui.dialogs.confirm(
564
+ w.ui.dialogs.confirm(
559
565
  type='preset_clear',
560
566
  id='',
561
567
  msg=trans('confirm.preset.clear'),
562
568
  )
563
569
  return
564
-
565
- self.window.core.config.set('prompt', "")
566
- self.window.core.config.set('ai_name', "")
567
- self.window.core.config.set('user_name', "")
568
- self.window.core.config.set('temperature', 1.0)
569
-
570
- if preset is not None and preset != "":
571
- if preset in self.window.core.presets.items:
572
- self.window.core.presets.items[preset].ai_name = ""
573
- self.window.core.presets.items[preset].user_name = ""
574
- self.window.core.presets.items[preset].prompt = ""
575
- self.window.core.presets.items[preset].temperature = 1.0
576
- self.refresh()
577
-
578
- self.window.update_status(trans('status.preset.cleared'))
579
-
580
- # reload assistant default instructions
581
- mode = self.window.core.config.get('mode')
570
+ w.core.config.set('prompt', "")
571
+ w.core.config.set('ai_name', "")
572
+ w.core.config.set('user_name', "")
573
+ w.core.config.set('temperature', 1.0)
574
+ if preset and preset in w.core.presets.items:
575
+ p = w.core.presets.items[preset]
576
+ p.ai_name = ""
577
+ p.user_name = ""
578
+ p.prompt = ""
579
+ p.temperature = 1.0
580
+ self.refresh()
581
+ w.update_status(trans('status.preset.cleared'))
582
+ mode = w.core.config.get('mode')
582
583
  if mode == MODE_ASSISTANT:
583
- self.window.core.assistants.load()
584
+ w.core.assistants.load()
584
585
 
585
586
  def delete(
586
587
  self,
@@ -594,25 +595,23 @@ class Presets:
594
595
  :param force: force delete without confirmation
595
596
  """
596
597
  if idx is not None:
597
- mode = self.window.core.config.get('mode')
598
- preset_id = self.window.core.presets.get_by_idx(idx, mode)
599
- if preset_id is not None and preset_id != "":
600
- if preset_id in self.window.core.presets.items:
601
- # if exists show confirmation dialog
602
- if not force:
603
- self.window.ui.dialogs.confirm(
604
- type='preset_delete',
605
- id=idx,
606
- msg=trans('confirm.preset.delete'),
607
- )
608
- return
609
-
610
- if preset_id == self.window.core.config.get('preset'):
611
- self.window.core.config.set('preset', None)
612
- self.window.ui.nodes['preset.prompt'].setPlainText("")
613
- self.window.core.presets.remove(preset_id, True)
614
- self.refresh(no_scroll=True)
615
- self.window.update_status(trans('status.preset.deleted'))
598
+ w = self.window
599
+ mode = w.core.config.get('mode')
600
+ preset_id = w.core.presets.get_by_idx(idx, mode)
601
+ if preset_id and preset_id in w.core.presets.items:
602
+ if not force:
603
+ w.ui.dialogs.confirm(
604
+ type='preset_delete',
605
+ id=idx,
606
+ msg=trans('confirm.preset.delete'),
607
+ )
608
+ return
609
+ if preset_id == w.core.config.get('preset'):
610
+ w.core.config.set('preset', None)
611
+ w.ui.nodes['preset.prompt'].setPlainText("")
612
+ w.core.presets.remove(preset_id, True)
613
+ self.refresh(no_scroll=True)
614
+ w.update_status(trans('status.preset.deleted'))
616
615
 
617
616
  def restore(self, force: bool = False):
618
617
  """
@@ -620,17 +619,18 @@ class Presets:
620
619
 
621
620
  :param force: force restore data
622
621
  """
622
+ w = self.window
623
623
  if not force:
624
- self.window.ui.dialogs.confirm(
624
+ w.ui.dialogs.confirm(
625
625
  type='preset_restore',
626
626
  id='',
627
627
  msg=trans('confirm.preset.restore'),
628
628
  )
629
629
  return
630
- mode = self.window.core.config.get('mode')
630
+ mode = w.core.config.get('mode')
631
631
  if mode == MODE_AGENT:
632
- mode = MODE_EXPERT # shared presets
633
- self.window.core.presets.restore(mode)
632
+ mode = MODE_EXPERT
633
+ w.core.presets.restore(mode)
634
634
  self.refresh()
635
635
 
636
636
  def is_current(self, idx: Optional[int] = None) -> bool:
@@ -641,11 +641,12 @@ class Presets:
641
641
  :return: True if current
642
642
  """
643
643
  if idx is not None:
644
- mode = self.window.core.config.get('mode')
644
+ w = self.window
645
+ mode = w.core.config.get('mode')
645
646
  if mode == MODE_AGENT:
646
- mode = MODE_EXPERT # shared presets
647
- preset_id = self.window.core.presets.get_by_idx(idx, mode)
648
- if preset_id is not None and preset_id != "":
647
+ mode = MODE_EXPERT
648
+ preset_id = w.core.presets.get_by_idx(idx, mode)
649
+ if preset_id:
649
650
  if preset_id == "current." + mode:
650
651
  return True
651
652
  return False
@@ -657,8 +658,7 @@ class Presets:
657
658
  :param value: filename
658
659
  :return: sanitized filename
659
660
  """
660
- # strip not allowed characters
661
- return re.sub(r'[^\w\s\-\.]', '', value)
661
+ return _VALIDATE_FILENAME_RE.sub('', value)
662
662
 
663
663
  def preset_change_locked(self) -> bool:
664
664
  """
@@ -666,12 +666,7 @@ class Presets:
666
666
 
667
667
  :return: True if locked
668
668
  """
669
- # if self.window.controller.chat.input.generating:
670
- # return True
671
- mode = self.window.core.config.get('mode')
672
- if mode == MODE_ASSISTANT:
673
- return True
674
- return False
669
+ return self.window.core.config.get('mode') == MODE_ASSISTANT
675
670
 
676
671
  def reload(self):
677
672
  """Reload presets"""
@@ -709,4 +704,4 @@ class Presets:
709
704
 
710
705
  def clear_selected(self):
711
706
  """Clear selected list"""
712
- self.selected = []
707
+ self.selected = []