pygpt-net 2.6.3__py3-none-any.whl → 2.6.5__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 (76) hide show
  1. pygpt_net/CHANGELOG.txt +10 -0
  2. pygpt_net/__init__.py +2 -2
  3. pygpt_net/config.py +55 -65
  4. pygpt_net/controller/__init__.py +5 -2
  5. pygpt_net/controller/chat/chat.py +38 -35
  6. pygpt_net/controller/chat/render.py +144 -217
  7. pygpt_net/controller/chat/stream.py +51 -25
  8. pygpt_net/controller/config/config.py +39 -42
  9. pygpt_net/controller/config/field/checkbox.py +16 -12
  10. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  11. pygpt_net/controller/config/field/cmd.py +51 -57
  12. pygpt_net/controller/config/field/combo.py +33 -16
  13. pygpt_net/controller/config/field/dictionary.py +48 -55
  14. pygpt_net/controller/config/field/input.py +50 -32
  15. pygpt_net/controller/config/field/slider.py +40 -45
  16. pygpt_net/controller/config/field/textarea.py +20 -6
  17. pygpt_net/controller/config/placeholder.py +110 -231
  18. pygpt_net/controller/ctx/common.py +48 -49
  19. pygpt_net/controller/ctx/ctx.py +24 -4
  20. pygpt_net/controller/lang/mapping.py +57 -95
  21. pygpt_net/controller/lang/plugins.py +64 -55
  22. pygpt_net/controller/lang/settings.py +39 -38
  23. pygpt_net/controller/layout/layout.py +11 -2
  24. pygpt_net/controller/plugins/plugins.py +19 -1
  25. pygpt_net/controller/settings/profile.py +16 -4
  26. pygpt_net/controller/ui/mode.py +107 -125
  27. pygpt_net/core/bridge/bridge.py +5 -5
  28. pygpt_net/core/command/command.py +149 -219
  29. pygpt_net/core/ctx/ctx.py +94 -146
  30. pygpt_net/core/debug/debug.py +48 -58
  31. pygpt_net/core/models/models.py +74 -112
  32. pygpt_net/core/modes/modes.py +13 -21
  33. pygpt_net/core/plugins/plugins.py +154 -177
  34. pygpt_net/core/presets/presets.py +103 -176
  35. pygpt_net/core/render/web/body.py +2 -3
  36. pygpt_net/core/render/web/renderer.py +109 -180
  37. pygpt_net/core/text/utils.py +28 -44
  38. pygpt_net/core/tokens/tokens.py +104 -203
  39. pygpt_net/data/config/config.json +3 -3
  40. pygpt_net/data/config/models.json +3 -3
  41. pygpt_net/item/ctx.py +141 -139
  42. pygpt_net/plugin/agent/plugin.py +2 -1
  43. pygpt_net/plugin/audio_output/plugin.py +5 -2
  44. pygpt_net/plugin/base/plugin.py +77 -93
  45. pygpt_net/plugin/bitbucket/plugin.py +3 -2
  46. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  47. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  48. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  49. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  50. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  51. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  52. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  53. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  54. pygpt_net/plugin/experts/plugin.py +2 -2
  55. pygpt_net/plugin/facebook/plugin.py +3 -4
  56. pygpt_net/plugin/github/plugin.py +4 -2
  57. pygpt_net/plugin/google/plugin.py +3 -3
  58. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  59. pygpt_net/plugin/mailer/plugin.py +3 -5
  60. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  61. pygpt_net/plugin/real_time/plugin.py +52 -60
  62. pygpt_net/plugin/slack/plugin.py +3 -4
  63. pygpt_net/plugin/telegram/plugin.py +3 -4
  64. pygpt_net/plugin/twitter/plugin.py +3 -4
  65. pygpt_net/tools/code_interpreter/tool.py +0 -1
  66. pygpt_net/tools/translator/tool.py +1 -1
  67. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  68. pygpt_net/ui/main.py +33 -29
  69. pygpt_net/ui/tray.py +61 -60
  70. pygpt_net/ui/widget/lists/context.py +2 -2
  71. pygpt_net/ui/widget/textarea/web.py +18 -14
  72. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.5.dist-info}/METADATA +12 -2
  73. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.5.dist-info}/RECORD +76 -76
  74. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.5.dist-info}/LICENSE +0 -0
  75. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.5.dist-info}/WHEEL +0 -0
  76. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.5.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.06.29 18:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Dict
@@ -34,8 +34,23 @@ class Textarea:
34
34
  :param key: Option key
35
35
  :param option: Option data
36
36
  """
37
- if key in self.window.ui.config[parent_id]:
38
- self.window.ui.config[parent_id][key].setText("{}".format(option["value"]))
37
+ parent = self.window.ui.config[parent_id]
38
+ field = parent.get(key)
39
+ if field is None:
40
+ return
41
+ new_text = str(option["value"])
42
+ if hasattr(field, "toPlainText"):
43
+ current = field.toPlainText()
44
+ elif hasattr(field, "text"):
45
+ current = field.text()
46
+ else:
47
+ current = None
48
+ if current == new_text:
49
+ return
50
+ if hasattr(field, "setText"):
51
+ field.setText(new_text)
52
+ elif hasattr(field, "setPlainText"):
53
+ field.setPlainText(new_text)
39
54
 
40
55
  def on_update(
41
56
  self,
@@ -57,9 +72,8 @@ class Textarea:
57
72
  option["value"] = value
58
73
  self.apply(parent_id, key, option)
59
74
 
60
- # on update hooks
61
75
  if hooks:
62
- hook_name = "update.{}.{}".format(parent_id, key)
76
+ hook_name = f"update.{parent_id}.{key}"
63
77
  if self.window.ui.has_hook(hook_name):
64
78
  hook = self.window.ui.get_hook(hook_name)
65
79
  try:
@@ -81,4 +95,4 @@ class Textarea:
81
95
  :param option: Option data
82
96
  :return: Textarea option value (plain text)
83
97
  """
84
- return self.window.ui.config[parent_id][key].toPlainText()
98
+ return self.window.ui.config[parent_id][key].toPlainText()
@@ -6,13 +6,12 @@
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.08 05:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict, Any, List
13
13
 
14
14
  from pygpt_net.core.types import (
15
- MODE_AGENT_LLAMA,
16
15
  MODE_LANGCHAIN,
17
16
  MODE_LLAMA_INDEX,
18
17
  AGENT_TYPE_OPENAI,
@@ -29,6 +28,54 @@ class Placeholder:
29
28
  :param window: Window instance
30
29
  """
31
30
  self.window = window
31
+ self._apply_handlers = {
32
+ "presets": lambda p: self.get_presets(p),
33
+ "modes": lambda p: self.get_modes(p),
34
+ "models": lambda p: self.get_models(p),
35
+ "languages": lambda p: self.get_languages(),
36
+ "multimodal": lambda p: self.get_multimodal(p),
37
+ "langchain_providers": lambda p: self.get_langchain_providers(),
38
+ "llama_index_providers": lambda p: self.get_llama_index_providers(),
39
+ "llm_providers": lambda p: self.get_llm_providers(),
40
+ "embeddings_providers": lambda p: self.get_embeddings_providers(),
41
+ "llama_index_loaders": lambda p: self.get_llama_index_loaders(),
42
+ "llama_index_loaders_file": lambda p: self.get_llama_index_loaders(type="file"),
43
+ "llama_index_loaders_web": lambda p: self.get_llama_index_loaders(type="web"),
44
+ "llama_index_chat_modes": lambda p: self.get_llama_index_chat_modes(),
45
+ "vector_storage": lambda p: self.get_vector_storage(),
46
+ "var_types": lambda p: self.get_var_types(),
47
+ "agent_modes": lambda p: self.get_agent_modes(),
48
+ "agent_provider": lambda p: self.get_agent_providers(),
49
+ "agent_provider_llama": lambda p: self.get_agent_providers_llama(),
50
+ "agent_provider_openai": lambda p: self.get_agent_providers_openai(),
51
+ "remote_tools_openai": lambda p: self.get_remote_tools_openai(),
52
+ "syntax_styles": lambda p: self.get_syntax_styles(),
53
+ "styles": lambda p: self.get_styles(),
54
+ "idx": lambda p: self.get_idx(p),
55
+ "keys": lambda p: self.get_keys(),
56
+ "keys_modifiers": lambda p: self.get_modifiers(),
57
+ "access_actions": lambda p: self.get_access_actions(),
58
+ "speech_synthesis_actions": lambda p: self.get_speech_synthesis_actions(),
59
+ "voice_control_actions": lambda p: self.get_voice_control_actions(),
60
+ "audio_input_devices": lambda p: self.get_audio_input_devices(),
61
+ "audio_output_devices": lambda p: self.get_audio_output_devices(),
62
+ "audio_input_backend": lambda p: self.get_audio_input_backend(),
63
+ "audio_output_backend": lambda p: self.get_audio_output_backend(),
64
+ "audio_tts_whisper_voices": lambda p: self.get_audio_tts_whisper_voices(),
65
+ }
66
+
67
+ def _apply_combo_if_needed(self, item: Any):
68
+ if isinstance(item, dict) and item.get("type") == "combo":
69
+ use = item.get("use")
70
+ if use is not None:
71
+ params = item.get("use_params")
72
+ if not isinstance(params, dict):
73
+ params = {}
74
+ item["keys"] = self.apply_by_id(use, params)
75
+
76
+ def _apply_suboptions(self, mapping: Dict[str, Any]):
77
+ for item in mapping.values():
78
+ self._apply_combo_if_needed(item)
32
79
 
33
80
  def apply(self, option: Dict[str, Any]):
34
81
  """
@@ -36,40 +83,18 @@ class Placeholder:
36
83
 
37
84
  :param option: Option dict
38
85
  """
39
- if option['type'] == 'dict' and 'keys' in option:
40
- for key in option['keys']:
41
- item = option['keys'][key]
42
- if type(item) is dict:
43
- if "type" in item:
44
- if item["type"] == "combo":
45
- if "use" in item and item["use"] is not None:
46
- params = {}
47
- if "use_params" in item and type(item["use_params"]) is dict:
48
- params = item["use_params"]
49
- item["keys"] = self.apply_by_id(item["use"], params)
50
- elif option['type'] == 'cmd' and 'params_keys' in option:
51
- for key in option['params_keys']:
52
- item = option['params_keys'][key]
53
- if type(item) is dict:
54
- if "type" in item:
55
- if item["type"] == "combo":
56
- if "use" in item and item["use"] is not None:
57
- params = {}
58
- if "use_params" in item and type(item["use_params"]) is dict:
59
- params = item["use_params"]
60
- item["keys"] = self.apply_by_id(item["use"], params)
61
- elif option['type'] == 'combo':
62
- if "use" in option and option["use"] is not None:
63
- params = {}
64
- if "use_params" in option and type(option["use_params"]) is dict:
65
- params = option["use_params"]
66
- option["keys"] = self.apply_by_id(option["use"], params)
67
- elif option['type'] == 'bool_list':
68
- if "use" in option and option["use"] is not None:
69
- params = {}
70
- if "use_params" in option and type(option["use_params"]) is dict:
71
- params = option["use_params"]
72
- option["keys"] = self.apply_by_id(option["use"], params)
86
+ t = option["type"]
87
+ if t == "dict" and "keys" in option:
88
+ self._apply_suboptions(option["keys"])
89
+ elif t == "cmd" and "params_keys" in option:
90
+ self._apply_suboptions(option["params_keys"])
91
+ elif t in ("combo", "bool_list"):
92
+ use = option.get("use")
93
+ if use is not None:
94
+ params = option.get("use_params")
95
+ if not isinstance(params, dict):
96
+ params = {}
97
+ option["keys"] = self.apply_by_id(use, params)
73
98
 
74
99
  def apply_by_id(self, id: str, params: dict = None) -> List[Dict[str, str]]:
75
100
  """
@@ -80,74 +105,8 @@ class Placeholder:
80
105
  """
81
106
  if params is None:
82
107
  params = {}
83
- if id == "presets":
84
- return self.get_presets(params)
85
- elif id == "modes":
86
- return self.get_modes(params)
87
- elif id == "models":
88
- return self.get_models(params)
89
- elif id == "languages":
90
- return self.get_languages()
91
- elif id == "multimodal":
92
- return self.get_multimodal(params)
93
- elif id == "langchain_providers":
94
- return self.get_langchain_providers()
95
- elif id == "llama_index_providers":
96
- return self.get_llama_index_providers()
97
- elif id == "llm_providers":
98
- return self.get_llm_providers()
99
- elif id == "embeddings_providers":
100
- return self.get_embeddings_providers()
101
- elif id == "llama_index_loaders":
102
- return self.get_llama_index_loaders()
103
- elif id == "llama_index_loaders_file":
104
- return self.get_llama_index_loaders(type="file")
105
- elif id == "llama_index_loaders_web":
106
- return self.get_llama_index_loaders(type="web")
107
- elif id == "llama_index_chat_modes":
108
- return self.get_llama_index_chat_modes()
109
- elif id == "vector_storage":
110
- return self.get_vector_storage()
111
- elif id == "var_types":
112
- return self.get_var_types()
113
- elif id == "agent_modes":
114
- return self.get_agent_modes()
115
- elif id == "agent_provider":
116
- return self.get_agent_providers()
117
- elif id == "agent_provider_llama":
118
- return self.get_agent_providers_llama()
119
- elif id == "agent_provider_openai":
120
- return self.get_agent_providers_openai()
121
- elif id == "remote_tools_openai":
122
- return self.get_remote_tools_openai()
123
- elif id == "syntax_styles":
124
- return self.get_syntax_styles()
125
- elif id == "styles":
126
- return self.get_styles()
127
- elif id == "idx":
128
- return self.get_idx(params)
129
- elif id == "keys":
130
- return self.get_keys()
131
- elif id == "keys_modifiers":
132
- return self.get_modifiers()
133
- elif id == "access_actions":
134
- return self.get_access_actions()
135
- elif id == "speech_synthesis_actions":
136
- return self.get_speech_synthesis_actions()
137
- elif id == "voice_control_actions":
138
- return self.get_voice_control_actions()
139
- elif id == "audio_input_devices":
140
- return self.get_audio_input_devices()
141
- elif id == "audio_output_devices":
142
- return self.get_audio_output_devices()
143
- elif id == "audio_input_backend":
144
- return self.get_audio_input_backend()
145
- elif id == "audio_output_backend":
146
- return self.get_audio_output_backend()
147
- elif id == "audio_tts_whisper_voices":
148
- return self.get_audio_tts_whisper_voices()
149
- else:
150
- return []
108
+ handler = self._apply_handlers.get(id)
109
+ return handler(params) if handler else []
151
110
 
152
111
  def get_audio_tts_whisper_voices(self) -> List[Dict[str, str]]:
153
112
  """
@@ -156,10 +115,7 @@ class Placeholder:
156
115
  :return: placeholders list
157
116
  """
158
117
  voices = self.window.core.audio.whisper.get_voices()
159
- data = []
160
- for voice in voices:
161
- data.append({voice: voice})
162
- return data
118
+ return [{v: v} for v in voices]
163
119
 
164
120
  def get_audio_input_devices(self) -> List[Dict[str, str]]:
165
121
  """
@@ -168,10 +124,7 @@ class Placeholder:
168
124
  :return: placeholders list
169
125
  """
170
126
  devices = self.window.core.audio.get_input_devices()
171
- data = []
172
- for device in devices:
173
- data.append({str(device[0]): device[1]})
174
- return data
127
+ return [{str(did): name} for did, name in devices]
175
128
 
176
129
  def get_audio_output_devices(self) -> List[Dict[str, str]]:
177
130
  """
@@ -180,10 +133,7 @@ class Placeholder:
180
133
  :return: placeholders list
181
134
  """
182
135
  devices = self.window.core.audio.get_output_devices()
183
- data = []
184
- for device in devices:
185
- data.append({str(device[0]): device[1]})
186
- return data
136
+ return [{str(did): name} for did, name in devices]
187
137
 
188
138
  def get_audio_input_backend(self) -> List[Dict[str, str]]:
189
139
  """
@@ -192,10 +142,7 @@ class Placeholder:
192
142
  :return: placeholders list
193
143
  """
194
144
  items = self.window.core.audio.get_input_backends()
195
- data = []
196
- for item in items:
197
- data.append({str(item[0]): item[1]})
198
- return data
145
+ return [{str(i): name} for i, name in items]
199
146
 
200
147
  def get_audio_output_backend(self) -> List[Dict[str, str]]:
201
148
  """
@@ -204,10 +151,7 @@ class Placeholder:
204
151
  :return: placeholders list
205
152
  """
206
153
  items = self.window.core.audio.get_output_backends()
207
- data = []
208
- for item in items:
209
- data.append({str(item[0]): item[1]})
210
- return data
154
+ return [{str(i): name} for i, name in items]
211
155
 
212
156
  def get_langchain_providers(self) -> List[Dict[str, str]]:
213
157
  """
@@ -216,10 +160,7 @@ class Placeholder:
216
160
  :return: placeholders list
217
161
  """
218
162
  choices = self.window.core.llm.get_choices(MODE_LANGCHAIN)
219
- data = []
220
- for id in choices:
221
- data.append({id: choices[id]})
222
- return data
163
+ return [{k: v} for k, v in choices.items()]
223
164
 
224
165
  def get_llama_index_providers(self) -> List[Dict[str, str]]:
225
166
  """
@@ -228,10 +169,7 @@ class Placeholder:
228
169
  :return: placeholders list
229
170
  """
230
171
  choices = self.window.core.llm.get_choices(MODE_LLAMA_INDEX)
231
- data = []
232
- for id in choices:
233
- data.append({id: choices[id]})
234
- return data
172
+ return [{k: v} for k, v in choices.items()]
235
173
 
236
174
  def get_llm_providers(self) -> List[Dict[str, str]]:
237
175
  """
@@ -240,10 +178,7 @@ class Placeholder:
240
178
  :return: placeholders list
241
179
  """
242
180
  choices = self.window.core.llm.get_choices()
243
- data = []
244
- for id in choices:
245
- data.append({id: choices[id]})
246
- return data
181
+ return [{k: v} for k, v in choices.items()]
247
182
 
248
183
  def get_embeddings_providers(self) -> List[Dict[str, str]]:
249
184
  """
@@ -252,10 +187,7 @@ class Placeholder:
252
187
  :return: placeholders list
253
188
  """
254
189
  choices = self.window.core.llm.get_choices("embeddings")
255
- data = []
256
- for id in choices:
257
- data.append({id: choices[id]})
258
- return data
190
+ return [{k: v} for k, v in choices.items()]
259
191
 
260
192
  def get_agent_providers(self) -> List[Dict[str, str]]:
261
193
  """
@@ -313,16 +245,16 @@ class Placeholder:
313
245
  :return: placeholders list
314
246
  """
315
247
  data = []
316
- choices = self.window.controller.idx.common.get_loaders_choices() # list
248
+ choices = self.window.controller.idx.common.get_loaders_choices()
317
249
  for choice in choices:
318
250
  if type == "all":
319
251
  data.append(choice)
320
252
  elif type == "file":
321
- key = list(choice.keys())[0]
253
+ key = next(iter(choice))
322
254
  if key.startswith("file_"):
323
255
  data.append(choice)
324
256
  elif type == "web":
325
- key = list(choice.keys())[0]
257
+ key = next(iter(choice))
326
258
  if key.startswith("web_"):
327
259
  data.append(choice)
328
260
  return data
@@ -334,10 +266,7 @@ class Placeholder:
334
266
  :return: placeholders list
335
267
  """
336
268
  ids = self.window.core.idx.storage.get_ids()
337
- data = []
338
- for id in ids:
339
- data.append({id: id})
340
- return data
269
+ return [{i: i} for i in ids]
341
270
 
342
271
  def get_var_types(self) -> List[Dict[str, str]]:
343
272
  """
@@ -346,10 +275,7 @@ class Placeholder:
346
275
  :return: placeholders list
347
276
  """
348
277
  types = ["str", "int", "float", "bool", "dict", "list", "None"]
349
- data = []
350
- for type in types:
351
- data.append({type: type})
352
- return data
278
+ return [{t: t} for t in types]
353
279
 
354
280
  def get_presets(self, params: dict = None) -> List[Dict[str, str]]:
355
281
  """
@@ -361,12 +287,8 @@ class Placeholder:
361
287
  if params is None:
362
288
  params = {}
363
289
  presets = self.window.core.presets.get_all()
364
- data = []
365
- data.append({'_': '---'})
366
- for id in presets:
367
- if id.startswith("current."): # ignore "current" preset
368
- continue
369
- data.append({id: id}) # TODO: name
290
+ data = [{'_': '---'}]
291
+ data.extend([{pid: pid} for pid in presets if not str(pid).startswith("current.")])
370
292
  return data
371
293
 
372
294
  def get_modes(self, params: dict = None) -> List[Dict[str, str]]:
@@ -379,11 +301,7 @@ class Placeholder:
379
301
  if params is None:
380
302
  params = {}
381
303
  modes = self.window.core.modes.get_all()
382
- data = []
383
- for id in modes:
384
- name = trans("mode." + id)
385
- data.append({id: name})
386
- return data
304
+ return [{mid: trans("mode." + mid)} for mid in modes]
387
305
 
388
306
  def get_multimodal(self, params: dict = None) -> List[Dict[str, str]]:
389
307
  """
@@ -395,11 +313,7 @@ class Placeholder:
395
313
  if params is None:
396
314
  params = {}
397
315
  modes = self.window.core.models.get_multimodal_list()
398
- data = []
399
- for id in modes:
400
- name = trans("multimodal." + id)
401
- data.append({id: name})
402
- return data
316
+ return [{mid: trans("multimodal." + mid)} for mid in modes]
403
317
 
404
318
  def get_models(self, params: dict = None) -> List[Dict[str, str]]:
405
319
  """
@@ -411,38 +325,29 @@ class Placeholder:
411
325
  if params is None:
412
326
  params = {}
413
327
  models = self.window.core.models.get_all()
414
- data = []
415
- items = {}
416
- for id in models:
417
- model = models[id]
328
+ items: Dict[str, str] = {}
329
+ for mid, model in models.items():
418
330
  allowed = True
419
- if "mode" in params and type(params["mode"]) is list:
420
- for mode in params["mode"]:
421
- if mode not in model.mode:
331
+ if isinstance(params.get("mode"), list):
332
+ for m in params["mode"]:
333
+ if m not in model.mode:
422
334
  allowed = False
423
335
  break
424
- if not allowed:
425
- continue
426
- items[id] = model.name
336
+ if allowed:
337
+ items[mid] = model.name
427
338
 
428
- # sort by providers
339
+ data: List[Dict[str, str]] = []
429
340
  providers = self.window.core.llm.get_choices()
430
- sorted_items = {}
431
- if len(providers) == 0:
432
- for id in items:
433
- sorted_items[id] = items[id]
434
- # sort by name
435
- sorted_items = dict(sorted(sorted_items.items(), key=lambda item: item[1].lower()))
436
- else:
437
- for provider in providers.keys():
438
- provider_items = {k: v for k, v in items.items() if models[k].provider == provider}
439
- if provider_items:
440
- sorted_items[f"separator::{provider}"] = providers[provider]
441
- sorted_items.update(provider_items)
442
-
443
- # make choices
444
- for id in sorted_items:
445
- data.append({id: sorted_items[id]})
341
+ if not providers:
342
+ for mid, name in sorted(items.items(), key=lambda kv: kv[1].lower()):
343
+ data.append({mid: name})
344
+ return data
345
+
346
+ for provider, provider_label in providers.items():
347
+ provider_items = [(k, v) for k, v in items.items() if models[k].provider == provider]
348
+ if provider_items:
349
+ data.append({f"separator::{provider}": provider_label})
350
+ data.extend([{k: v} for k, v in provider_items])
446
351
  return data
447
352
 
448
353
  def get_agent_modes(self) -> List[Dict[str, str]]:
@@ -452,11 +357,7 @@ class Placeholder:
452
357
  :return: Models placeholders list
453
358
  """
454
359
  modes = self.window.core.agents.legacy.get_allowed_modes()
455
- data = []
456
- for id in modes:
457
- name = trans(f"mode.{id}")
458
- data.append({id: name})
459
- return data
360
+ return [{mid: trans(f"mode.{mid}")} for mid in modes]
460
361
 
461
362
  def get_languages(self) -> List[Dict[str, str]]:
462
363
  """
@@ -492,10 +393,7 @@ class Placeholder:
492
393
  """
493
394
  styles = self.window.controller.chat.render.web_renderer.body.highlight.get_styles()
494
395
  styles.sort()
495
- data = []
496
- for id in styles:
497
- data.append({id: id})
498
- return data
396
+ return [{sid: sid} for sid in styles]
499
397
 
500
398
  def get_styles(self) -> List[Dict[str, str]]:
501
399
  """
@@ -505,11 +403,7 @@ class Placeholder:
505
403
  """
506
404
  styles = self.window.controller.theme.common.get_styles_list()
507
405
  styles.sort()
508
- data = []
509
- for id in styles:
510
- name = id
511
- data.append({id: name})
512
- return data
406
+ return [{sid: sid} for sid in styles]
513
407
 
514
408
  def get_keys(self) -> List[Dict[str, str]]:
515
409
  """
@@ -527,6 +421,11 @@ class Placeholder:
527
421
  """
528
422
  return self.window.core.access.shortcuts.get_modifiers_choices()
529
423
 
424
+ def _translate_sort_choices(self, choices: List[Dict[str, str]]) -> List[Dict[str, str]]:
425
+ items = [(k, trans(v)) for choice in choices for k, v in choice.items()]
426
+ items.sort(key=lambda x: x[1])
427
+ return [{k: v} for k, v in items]
428
+
530
429
  def get_access_actions(self) -> List[Dict[str, str]]:
531
430
  """
532
431
  Get access actions list
@@ -534,13 +433,7 @@ class Placeholder:
534
433
  :return: app actions list
535
434
  """
536
435
  choices = self.window.core.access.actions.get_access_choices()
537
- translated_choices = []
538
- for choice in choices:
539
- for key, value in choice.items():
540
- translated_choices.append({key: trans(value)})
541
- # sort by translated values
542
- translated_choices.sort(key=lambda x: list(x.values())[0])
543
- return translated_choices
436
+ return self._translate_sort_choices(choices)
544
437
 
545
438
  def get_speech_synthesis_actions(self) -> List[Dict[str, str]]:
546
439
  """
@@ -549,13 +442,7 @@ class Placeholder:
549
442
  :return: app actions list
550
443
  """
551
444
  choices = self.window.core.access.actions.get_speech_synthesis_choices()
552
- translated_choices = []
553
- for choice in choices:
554
- for key, value in choice.items():
555
- translated_choices.append({key: trans(value)})
556
- # sort by translated values
557
- translated_choices.sort(key=lambda x: list(x.values())[0])
558
- return translated_choices
445
+ return self._translate_sort_choices(choices)
559
446
 
560
447
  def get_voice_control_actions(self) -> List[Dict[str, str]]:
561
448
  """
@@ -564,12 +451,4 @@ class Placeholder:
564
451
  :return: app actions list
565
452
  """
566
453
  choices = self.window.core.access.actions.get_voice_control_choices()
567
- translated_choices = []
568
- for choice in choices:
569
- for key, value in choice.items():
570
- translated_choices.append({key: trans(value)})
571
- # sort by translated values
572
- translated_choices.sort(key=lambda x: list(x.values())[0])
573
- return translated_choices
574
-
575
-
454
+ return self._translate_sort_choices(choices)