pygpt-net 2.6.3__py3-none-any.whl → 2.6.4__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 (66) hide show
  1. pygpt_net/CHANGELOG.txt +5 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/config.py +55 -65
  4. pygpt_net/controller/chat/chat.py +38 -35
  5. pygpt_net/controller/chat/render.py +144 -217
  6. pygpt_net/controller/chat/stream.py +51 -25
  7. pygpt_net/controller/config/config.py +39 -42
  8. pygpt_net/controller/config/field/checkbox.py +16 -12
  9. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  10. pygpt_net/controller/config/field/cmd.py +51 -57
  11. pygpt_net/controller/config/field/combo.py +33 -16
  12. pygpt_net/controller/config/field/dictionary.py +48 -55
  13. pygpt_net/controller/config/field/input.py +50 -32
  14. pygpt_net/controller/config/field/slider.py +40 -45
  15. pygpt_net/controller/config/field/textarea.py +20 -6
  16. pygpt_net/controller/config/placeholder.py +110 -231
  17. pygpt_net/controller/lang/mapping.py +57 -95
  18. pygpt_net/controller/lang/plugins.py +64 -55
  19. pygpt_net/controller/lang/settings.py +39 -38
  20. pygpt_net/controller/layout/layout.py +11 -2
  21. pygpt_net/controller/plugins/plugins.py +19 -1
  22. pygpt_net/controller/ui/mode.py +107 -125
  23. pygpt_net/core/bridge/bridge.py +5 -5
  24. pygpt_net/core/command/command.py +149 -219
  25. pygpt_net/core/ctx/ctx.py +94 -146
  26. pygpt_net/core/debug/debug.py +48 -58
  27. pygpt_net/core/models/models.py +74 -112
  28. pygpt_net/core/modes/modes.py +13 -21
  29. pygpt_net/core/plugins/plugins.py +154 -177
  30. pygpt_net/core/presets/presets.py +103 -176
  31. pygpt_net/core/render/web/body.py +2 -3
  32. pygpt_net/core/render/web/renderer.py +109 -180
  33. pygpt_net/core/text/utils.py +28 -44
  34. pygpt_net/core/tokens/tokens.py +104 -203
  35. pygpt_net/data/config/config.json +2 -2
  36. pygpt_net/data/config/models.json +2 -2
  37. pygpt_net/item/ctx.py +141 -139
  38. pygpt_net/plugin/agent/plugin.py +2 -1
  39. pygpt_net/plugin/audio_output/plugin.py +5 -2
  40. pygpt_net/plugin/base/plugin.py +77 -93
  41. pygpt_net/plugin/bitbucket/plugin.py +3 -2
  42. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  43. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  44. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  45. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  46. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  47. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  48. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  49. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  50. pygpt_net/plugin/experts/plugin.py +2 -2
  51. pygpt_net/plugin/facebook/plugin.py +3 -4
  52. pygpt_net/plugin/github/plugin.py +4 -2
  53. pygpt_net/plugin/google/plugin.py +3 -3
  54. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  55. pygpt_net/plugin/mailer/plugin.py +3 -5
  56. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  57. pygpt_net/plugin/real_time/plugin.py +52 -60
  58. pygpt_net/plugin/slack/plugin.py +3 -4
  59. pygpt_net/plugin/telegram/plugin.py +3 -4
  60. pygpt_net/plugin/twitter/plugin.py +3 -4
  61. pygpt_net/ui/widget/textarea/web.py +18 -14
  62. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/METADATA +7 -2
  63. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/RECORD +66 -66
  64. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/LICENSE +0 -0
  65. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.dist-info}/WHEEL +0 -0
  66. {pygpt_net-2.6.3.dist-info → pygpt_net-2.6.4.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.11 18:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import gc
@@ -38,6 +38,7 @@ class Debug:
38
38
  self.window = window
39
39
  self.console = Console(window)
40
40
  self.pause_idx = 1
41
+ self._process = psutil.Process(os.getpid())
41
42
 
42
43
  @staticmethod
43
44
  def init(level: int = logging.ERROR):
@@ -46,13 +47,13 @@ class Debug:
46
47
 
47
48
  :param level: log level (default: ERROR)
48
49
  """
49
- if not Config.prepare_workdir():
50
- os.makedirs(Config.prepare_workdir())
50
+ workdir = Config.prepare_workdir()
51
+ Path(workdir).mkdir(parents=True, exist_ok=True)
51
52
 
52
53
  logging.basicConfig(
53
54
  level=level,
54
55
  format='%(asctime)s - %(levelname)s - %(message)s',
55
- filename=str(Path(os.path.join(Config.prepare_workdir(), 'app.log'))),
56
+ filename=str(Path(workdir) / 'app.log'),
56
57
  filemode='a',
57
58
  encoding='utf-8',
58
59
  )
@@ -66,11 +67,13 @@ class Debug:
66
67
  :param tb: traceback
67
68
  """
68
69
  logger = logging.getLogger()
69
- if not hasattr(logging, '_is_handling_exception'):
70
- logging._is_handling_exception = True
71
- logger.error("Uncaught exception:", exc_info=(exc_type, value, tb))
72
- traceback.print_exception(exc_type, value, tb)
73
- del logging._is_handling_exception # remove flag when done
70
+ if not getattr(handle_exception, "_handling", False):
71
+ handle_exception._handling = True
72
+ try:
73
+ logger.error("Uncaught exception:", exc_info=(exc_type, value, tb))
74
+ traceback.print_exception(exc_type, value, tb)
75
+ finally:
76
+ handle_exception._handling = False
74
77
  else:
75
78
  traceback.print_exception(exc_type, value, tb)
76
79
 
@@ -84,7 +87,7 @@ class Debug:
84
87
  for handler in logger.handlers[:]:
85
88
  logger.removeHandler(handler)
86
89
  handler.close()
87
- file_handler = logging.FileHandler(filename=Path(path), mode='a', encoding='utf-8')
90
+ file_handler = logging.FileHandler(filename=str(Path(path)), mode='a', encoding='utf-8')
88
91
  formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
89
92
  file_handler.setFormatter(formatter)
90
93
  file_handler.setLevel(level)
@@ -124,16 +127,13 @@ class Debug:
124
127
  :param id: log level id
125
128
  :return: log level name
126
129
  """
127
- if id == logging.ERROR:
128
- return "error"
129
- elif id == logging.WARNING:
130
- return "warning"
131
- elif id == logging.INFO:
132
- return "info"
133
- elif id == logging.DEBUG:
134
- return "debug"
135
- else:
136
- return "unknown"
130
+ mapping = {
131
+ logging.ERROR: "error",
132
+ logging.WARNING: "warning",
133
+ logging.INFO: "info",
134
+ logging.DEBUG: "debug",
135
+ }
136
+ return mapping.get(id, "unknown")
137
137
 
138
138
  def info(self, message: Any = None, console: bool = True):
139
139
  """
@@ -200,43 +200,36 @@ class Debug:
200
200
  return
201
201
 
202
202
  logger = logging.getLogger()
203
+ enabled = self.has_level(level)
203
204
 
204
205
  try:
205
- # string message
206
206
  if isinstance(message, str):
207
- if self.has_level(level):
207
+ if enabled:
208
208
  logger.log(level, message)
209
209
  if console:
210
210
  print(message)
211
-
212
- # exception
213
211
  elif isinstance(message, Exception):
214
212
  is_sys, data = self.parse_exception(message)
215
- msg = "Exception: {}".format(str(message))
213
+ msg = f"Exception: {message}"
216
214
  if not is_sys:
217
- msg += "\n{}".format(data)
215
+ msg += f"\n{data}"
218
216
  logger.log(level, msg, exc_info=is_sys)
219
- if self.has_level(level) and console and data:
217
+ if enabled and console and data:
220
218
  print(data)
221
-
222
- # other messages
223
219
  else:
224
- if self.has_level(level):
220
+ if enabled:
225
221
  logger.log(level, message)
226
222
  if console:
227
223
  print(message)
228
- except Exception as e:
224
+ except Exception:
229
225
  pass
230
226
 
231
227
  try:
232
- # send to logger window
233
- if self.has_level(level) and self.window is not None:
234
- # append thread ID
235
- thread_suffix = ""
236
- if not threading.current_thread() is threading.main_thread():
237
- thread_suffix = " [THREAD: {}]".format(threading.current_thread().ident)
228
+ if enabled and self.window is not None:
229
+ t = threading.current_thread()
230
+ thread_suffix = "" if t is threading.main_thread() else f" [THREAD: {t.ident}]"
238
231
  self.window.logger_message.emit(str(message) + thread_suffix)
239
- except Exception as e:
232
+ except Exception:
240
233
  pass
241
234
 
242
235
  def parse_exception(self, e: Any = None, limit: int = 4) -> Tuple[bool, str]:
@@ -249,9 +242,9 @@ class Debug:
249
242
  """
250
243
  is_sys = False
251
244
  type_name = ""
252
- etype, value, tb = sys.exc_info() # from sys by default
245
+ etype, value, tb = sys.exc_info()
253
246
  if etype is None and e is not None:
254
- tb = e.__traceback__ # from exception
247
+ tb = e.__traceback__
255
248
  type_name = type(e).__name__
256
249
  value = str(e)
257
250
  else:
@@ -259,7 +252,6 @@ class Debug:
259
252
  is_sys = True
260
253
  type_name = etype.__name__
261
254
 
262
- # traceback
263
255
  traceback_details = traceback.extract_tb(tb)
264
256
  if len(traceback_details) >= limit:
265
257
  last_calls = traceback_details[-limit:]
@@ -269,7 +261,6 @@ class Debug:
269
261
  if last_calls:
270
262
  formatted_traceback = "".join(traceback.format_list(last_calls))
271
263
 
272
- # parse data
273
264
  data = ""
274
265
  if type_name:
275
266
  data += "Type: {}".format(type_name)
@@ -299,7 +290,7 @@ class Debug:
299
290
  :param level: logging level
300
291
  :return: True if enabled
301
292
  """
302
- return level >= logging.getLogger().getEffectiveLevel()
293
+ return logging.getLogger().isEnabledFor(level)
303
294
 
304
295
  def enabled(self) -> bool:
305
296
  """
@@ -346,8 +337,7 @@ class Debug:
346
337
  :param label: label for memory usage
347
338
  :return: formatted memory usage string
348
339
  """
349
- process = psutil.Process(os.getpid())
350
- mem_mb = process.memory_info().rss / (1024 * 1024)
340
+ mem_mb = self._process.memory_info().rss / (1024 * 1024)
351
341
  data = f"Memory Usage: {mem_mb:.2f} MB"
352
342
  print(f"[{label}] {data}")
353
343
  return data
@@ -376,24 +366,25 @@ class Debug:
376
366
  res += self.print_memory_usage(label)
377
367
 
378
368
  try:
379
- from pympler import asizeof, summary, muppy # pip install pympler
369
+ from pympler import asizeof, summary, muppy
380
370
  objs = muppy.get_objects()
381
- # objs_by_type = muppy.filter(objs, Type=dict)
382
371
  sum_by_type = summary.summarize(objs)
383
- # sum_by_type = summary.summarize(objs_by_type)
384
372
  summary.print_(sum_by_type)
385
373
 
386
- total_bytes = asizeof.asizeof(self.window.controller.chat.render.web_renderer.pids)
374
+ pids = self.window.controller.chat.render.web_renderer.pids
375
+ total_bytes = asizeof.asizeof(pids)
387
376
  pids_total_mb = total_bytes / (1024 * 1024)
388
- count_pids = len(self.window.controller.chat.render.web_renderer.pids)
377
+ count_pids = len(pids)
389
378
 
390
- total_bytes = asizeof.asizeof(self.window.core.ctx.meta)
379
+ meta = self.window.core.ctx.meta
380
+ total_bytes = asizeof.asizeof(meta)
391
381
  meta_total_mb = total_bytes / (1024 * 1024)
392
- count_meta = len(self.window.core.ctx.meta)
382
+ count_meta = len(meta)
393
383
 
394
- total_bytes = asizeof.asizeof(self.window.core.ctx.get_items())
384
+ ctx_items = self.window.core.ctx.get_items()
385
+ total_bytes = asizeof.asizeof(ctx_items)
395
386
  ctx_total_mb = total_bytes / (1024 * 1024)
396
- count_ctx = len(self.window.core.ctx.get_items())
387
+ count_ctx = len(ctx_items)
397
388
 
398
389
  stats.append(f"Pids: {pids_total_mb:.4f} MB ({count_pids})")
399
390
  stats.append(f"CtxMeta: {meta_total_mb:.4f} MB ({count_meta})")
@@ -420,14 +411,13 @@ class Debug:
420
411
  :param args: objects to dump
421
412
  """
422
413
  dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
423
- thread_info = "[MAIN THREAD]" if threading.current_thread() is threading.main_thread() \
424
- else f"[THREAD: {threading.current_thread().ident}]"
414
+ t = threading.current_thread()
415
+ thread_info = "[MAIN THREAD]" if t is threading.main_thread() else f"[THREAD: {t.ident}]"
425
416
  print(f"\n{Color.FAIL}{Color.BOLD}<DEBUG: PAUSED> #{self.pause_idx} {dt}{Color.ENDC}")
426
417
  print(f"\n{Color.BOLD}{thread_info}{Color.ENDC}")
427
418
  print("------------------------------>")
428
419
  self.pause_idx += 1
429
420
 
430
- # dump args
431
421
  for index, arg in enumerate(args):
432
422
  print(f"\n{Color.BOLD}Dump {index + 1}:{Color.ENDC}")
433
423
  print(f"{Color.BOLD}Type: {type(arg)}{Color.ENDC}")
@@ -439,4 +429,4 @@ class Debug:
439
429
  traceback.print_stack()
440
430
 
441
431
  input(f"<------------------------------\n\n{Color.OKGREEN}Paused. Press Enter to continue...{Color.ENDC}")
442
- print(f"------------------------------\n{Color.OKGREEN}{Color.BOLD}<RESUMED>{Color.ENDC}\n")
432
+ print(f"------------------------------\n{Color.OKGREEN}{Color.BOLD}<RESUMED>{Color.ENDC}\n")
@@ -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.08 19:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -72,47 +72,37 @@ class Models:
72
72
  """
73
73
  base_items = self.get_base()
74
74
  updated = False
75
- added_keys = []
76
75
 
77
- # check for missing keys
78
- for key in base_items:
76
+ for key, base in base_items.items():
79
77
  if key not in self.items:
80
- self.items[key] = copy.deepcopy(base_items[key])
78
+ self.items[key] = copy.deepcopy(base)
81
79
  updated = True
82
80
 
83
- # check for missing models ids
84
- for key in base_items:
85
- model_id = base_items[key].id
86
- old_exists = False
87
- for old_key in self.items:
88
- if self.items[old_key].id == model_id:
89
- old_exists = True
90
- break
91
- if not old_exists and key not in added_keys:
92
- self.items[key] = copy.deepcopy(base_items[key])
93
- added_keys.append(key)
81
+ existing_ids = {it.id for it in self.items.values()}
82
+ for key, base in base_items.items():
83
+ if base.id not in existing_ids:
84
+ self.items[key] = copy.deepcopy(base)
85
+ existing_ids.add(base.id)
94
86
  updated = True
95
87
 
96
- # update multimodal options
97
- for key in self.items:
98
- if key in base_items:
99
- if base_items[key].input != self.items[key].input:
100
- self.items[key].input = base_items[key].input
101
- updated = True
102
- if base_items[key].output != self.items[key].output:
103
- self.items[key].output = base_items[key].output
104
- updated = True
105
-
106
- # update empty multimodal options
107
- for key in self.items:
108
- if isinstance(self.items[key].input, list):
109
- if len(self.items[key].input) == 0:
110
- self.items[key].input = ["text"]
111
- updated = True
112
- if isinstance(self.items[key].output, list):
113
- if len(self.items[key].output) == 0:
114
- self.items[key].output = ["text"]
115
- updated = True
88
+ for key, base in base_items.items():
89
+ item = self.items.get(key)
90
+ if not item:
91
+ continue
92
+ if base.input != item.input:
93
+ item.input = base.input
94
+ updated = True
95
+ if base.output != item.output:
96
+ item.output = base.output
97
+ updated = True
98
+
99
+ for item in self.items.values():
100
+ if isinstance(item.input, list) and not item.input:
101
+ item.input = ["text"]
102
+ updated = True
103
+ if isinstance(item.output, list) and not item.output:
104
+ item.output = ["text"]
105
+ updated = True
116
106
 
117
107
  if updated:
118
108
  self.save()
@@ -126,8 +116,7 @@ class Models:
126
116
  :param key: model name
127
117
  :return: model config object
128
118
  """
129
- if key in self.items:
130
- return self.items[key]
119
+ return self.items.get(key)
131
120
 
132
121
  def get_ids(self) -> List[str]:
133
122
  """
@@ -167,9 +156,7 @@ class Models:
167
156
  :param mode: mode name
168
157
  :return: True if model is allowed for mode
169
158
  """
170
- if model in self.items:
171
- return mode in self.items[model].mode
172
- return False
159
+ return model in self.items and mode in self.items[model].mode
173
160
 
174
161
  def get_id(
175
162
  self,
@@ -181,8 +168,8 @@ class Models:
181
168
  :param key: model key
182
169
  :return: model id
183
170
  """
184
- if key in self.items:
185
- return self.items[key].id
171
+ item = self.items.get(key)
172
+ return item.id if item else None
186
173
 
187
174
  def get_by_idx(
188
175
  self,
@@ -209,11 +196,7 @@ class Models:
209
196
  :param mode: mode name
210
197
  :return: models dict for mode
211
198
  """
212
- items = {}
213
- for key in self.items:
214
- if mode in self.items[key].mode:
215
- items[key] = self.items[key]
216
- return items
199
+ return {k: v for k, v in self.items.items() if mode in v.mode}
217
200
 
218
201
  def get_next(
219
202
  self,
@@ -259,10 +242,17 @@ class Models:
259
242
 
260
243
  :return: new model id
261
244
  """
262
- id = "model-000"
263
- while id in self.items:
264
- id = "model-" + str(int(id.split("-")[1]) + 1).zfill(3)
265
- return id
245
+ prefix = "model-"
246
+ used = set()
247
+ for k in self.items.keys():
248
+ if isinstance(k, str) and k.startswith(prefix):
249
+ suffix = k[len(prefix):]
250
+ if suffix.isdigit():
251
+ used.add(int(suffix))
252
+ n = 0
253
+ while n in used:
254
+ n += 1
255
+ return f"{prefix}{n:03d}"
266
256
 
267
257
  def get_multimodal_list(self) -> List[str]:
268
258
  """
@@ -336,8 +326,8 @@ class Models:
336
326
  :param model: model name
337
327
  :return: True if model exists for mode
338
328
  """
339
- items = self.get_by_mode(mode)
340
- return model in items
329
+ item = self.items.get(model)
330
+ return bool(item and mode in item.mode)
341
331
 
342
332
  def get_default(self, mode: str) -> Optional[str]:
343
333
  """
@@ -346,13 +336,10 @@ class Models:
346
336
  :param mode: mode name
347
337
  :return: default model name
348
338
  """
349
- models = {}
350
339
  items = self.get_by_mode(mode)
351
- for k in items:
352
- models[k] = items[k]
353
- if len(models) == 0:
340
+ if not items:
354
341
  return None
355
- return list(models.keys())[0]
342
+ return next(iter(items))
356
343
 
357
344
  def get_tokens(self, model: str) -> int:
358
345
  """
@@ -385,12 +372,10 @@ class Models:
385
372
 
386
373
  :param model: model name
387
374
  """
388
- # restore all models
389
375
  if model is None:
390
376
  self.load_base()
391
377
  return
392
378
 
393
- # restore single model
394
379
  items = self.provider.load_base()
395
380
  if model in items:
396
381
  self.items[model] = items[model]
@@ -435,34 +420,24 @@ class Models:
435
420
  :return: mode (supported)
436
421
  """
437
422
  prev_mode = mode
438
- # if OpenAI API model and not llama_index mode, switch to Chat mode
439
- if model.is_supported(MODE_CHAT) and mode != MODE_LLAMA_INDEX: # do not switch if llama_index mode!
423
+ if model.is_supported(MODE_CHAT) and mode != MODE_LLAMA_INDEX:
440
424
  if prev_mode != MODE_CHAT:
441
425
  self.window.core.debug.info(
442
426
  "WARNING: Switching to chat mode (model not supported in: {})".format(prev_mode))
443
427
  return MODE_CHAT
444
428
 
445
- # Research / Perplexity
446
429
  if model.is_supported(MODE_RESEARCH):
447
430
  if prev_mode != MODE_RESEARCH:
448
431
  self.window.core.debug.info(
449
432
  "WARNING: Switching to research mode (model not supported in: {})".format(mode))
450
433
  mode = MODE_RESEARCH
451
434
 
452
- # Llama Index / Chat with Files
453
435
  elif model.is_supported(MODE_LLAMA_INDEX):
454
436
  if prev_mode != MODE_LLAMA_INDEX:
455
437
  self.window.core.debug.info(
456
438
  "WARNING: Switching to llama_index mode (model not supported in: {})".format(mode))
457
439
  mode = MODE_LLAMA_INDEX
458
440
 
459
- # LangChain
460
- """
461
- elif model.is_supported(MODE_LANGCHAIN):
462
- self.window.core.debug.info(
463
- "WARNING: Switching to langchain mode (model not supported in: {})".format(mode))
464
- mode = MODE_LANGCHAIN
465
- """
466
441
  return mode
467
442
 
468
443
  def prepare_client_args(
@@ -477,65 +452,55 @@ class Models:
477
452
  :param model: ModelItem
478
453
  :return: client arguments dict
479
454
  """
455
+ cfg = self.window.core.config
480
456
  args = {
481
- "api_key": self.window.core.config.get('api_key'),
482
- "organization": self.window.core.config.get('organization_key'),
457
+ "api_key": cfg.get('api_key'),
458
+ "organization": cfg.get('organization_key'),
483
459
  }
484
- # api endpoint
485
- if self.window.core.config.has('api_endpoint'):
486
- endpoint = self.window.core.config.get('api_endpoint')
460
+
461
+ if cfg.has('api_endpoint'):
462
+ endpoint = cfg.get('api_endpoint')
487
463
  if endpoint:
488
464
  args["base_url"] = endpoint
489
- # proxy
490
- if self.window.core.config.has('api_proxy'):
491
- proxy = self.window.core.config.get('api_proxy')
465
+
466
+ if cfg.has('api_proxy'):
467
+ proxy = cfg.get('api_proxy')
492
468
  if proxy:
493
469
  transport = SyncProxyTransport.from_url(proxy)
494
- args["http_client"] = DefaultHttpxClient(
495
- transport=transport,
496
- )
470
+ args["http_client"] = DefaultHttpxClient(transport=transport)
497
471
 
498
- # research mode endpoint - Perplexity
499
472
  if model is not None:
500
- # xAI / grok
501
473
  if model.provider == "x_ai":
502
- args["api_key"] = self.window.core.config.get('api_key_xai', "")
503
- args["base_url"] = self.window.core.config.get('api_endpoint_xai', "")
474
+ args["api_key"] = cfg.get('api_key_xai', "")
475
+ args["base_url"] = cfg.get('api_endpoint_xai', "")
504
476
  self.window.core.debug.info("[api] Using client: xAI")
505
- # Perplexity
506
477
  elif model.provider == "perplexity":
507
- args["api_key"] = self.window.core.config.get('api_key_perplexity', "")
508
- args["base_url"] = self.window.core.config.get('api_endpoint_perplexity', "")
478
+ args["api_key"] = cfg.get('api_key_perplexity', "")
479
+ args["base_url"] = cfg.get('api_endpoint_perplexity', "")
509
480
  self.window.core.debug.info("[api] Using client: Perplexity")
510
- # Google
511
481
  elif model.provider == "google":
512
- args["api_key"] = self.window.core.config.get('api_key_google', "")
513
- args["base_url"] = self.window.core.config.get('api_endpoint_google', "")
482
+ args["api_key"] = cfg.get('api_key_google', "")
483
+ args["base_url"] = cfg.get('api_endpoint_google', "")
514
484
  self.window.core.debug.info("[api] Using client: Google")
515
- # Anthropic
516
485
  elif model.provider == "anthropic":
517
- args["api_key"] = self.window.core.config.get('api_key_anthropic', "")
518
- args["base_url"] = self.window.core.config.get('api_endpoint_anthropic', "")
486
+ args["api_key"] = cfg.get('api_key_anthropic', "")
487
+ args["base_url"] = cfg.get('api_endpoint_anthropic', "")
519
488
  self.window.core.debug.info("[api] Using client: Anthropic")
520
- # Deepseek
521
489
  elif model.provider == "deepseek_api":
522
- args["api_key"] = self.window.core.config.get('api_key_deepseek', "")
523
- args["base_url"] = self.window.core.config.get('api_endpoint_deepseek', "")
490
+ args["api_key"] = cfg.get('api_key_deepseek', "")
491
+ args["base_url"] = cfg.get('api_endpoint_deepseek', "")
524
492
  self.window.core.debug.info("[api] Using client: Deepseek API")
525
- # Mistral AI
526
493
  elif model.provider == "mistral_ai":
527
- args["api_key"] = self.window.core.config.get('api_key_mistral', "")
528
- args["base_url"] = self.window.core.config.get('api_endpoint_mistral', "")
494
+ args["api_key"] = cfg.get('api_key_mistral', "")
495
+ args["base_url"] = cfg.get('api_endpoint_mistral', "")
529
496
  self.window.core.debug.info("[api] Using client: Mistral AI API")
530
- # HuggingFace Router
531
497
  elif model.provider == "huggingface_router":
532
- args["api_key"] = self.window.core.config.get('api_key_hugging_face', "")
533
- args["base_url"] = self.window.core.config.get('api_endpoint_hugging_face', "")
498
+ args["api_key"] = cfg.get('api_key_hugging_face', "")
499
+ args["base_url"] = cfg.get('api_endpoint_hugging_face', "")
534
500
  self.window.core.debug.info("[api] Using client: HuggingFace Router API")
535
501
  else:
536
502
  self.window.core.debug.info("[api] Using client: OpenAI (default)")
537
503
 
538
- # do not include organization for non-OpenAI providers
539
504
  if model.provider != "openai":
540
505
  if "organization" in args:
541
506
  del args["organization"]
@@ -554,12 +519,9 @@ class Models:
554
519
  if mode == MODE_LLAMA_INDEX:
555
520
  if model.provider == "google":
556
521
  stream = self.window.core.config.get('stream', False)
557
- use_react = self.window.core.config.get("llama.idx.react", False) # use ReAct agent for tool calls
522
+ use_react = self.window.core.config.get("llama.idx.react", False)
558
523
  if stream:
559
- if use_react:
560
- return True
561
- else:
562
- return False
524
+ return bool(use_react)
563
525
  if model.tool_calls:
564
526
  return True
565
527
  return False
@@ -570,4 +532,4 @@ class Models:
570
532
 
571
533
  :return: config version
572
534
  """
573
- return self.provider.get_version()
535
+ return self.provider.get_version()
@@ -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.30 00:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict, List
@@ -41,7 +41,7 @@ class Modes:
41
41
  self.window = window
42
42
  self.provider = JsonFileProvider(window)
43
43
  self.initialized = False
44
- self.all = [
44
+ self.all = (
45
45
  MODE_AGENT,
46
46
  MODE_AGENT_LLAMA,
47
47
  MODE_AGENT_OPENAI,
@@ -56,7 +56,7 @@ class Modes:
56
56
  MODE_VISION,
57
57
  MODE_RESEARCH,
58
58
  MODE_COMPUTER,
59
- ]
59
+ )
60
60
  self.items = {}
61
61
 
62
62
  def get_by_idx(self, idx) -> str:
@@ -66,8 +66,8 @@ class Modes:
66
66
  :param idx: index of mode
67
67
  :return: mode name
68
68
  """
69
- modes = self.get_all()
70
- return list(modes.keys())[idx]
69
+ keys = tuple(self.items)
70
+ return keys[idx]
71
71
 
72
72
  def get_idx_by_name(self, name) -> int:
73
73
  """
@@ -76,8 +76,8 @@ class Modes:
76
76
  :param name: mode name
77
77
  :return: mode index
78
78
  """
79
- modes = self.get_all()
80
- return list(modes.keys()).index(name)
79
+ keys = tuple(self.items)
80
+ return keys.index(name)
81
81
 
82
82
  def get_all(self) -> Dict[str, List[str]]:
83
83
  """
@@ -93,9 +93,7 @@ class Modes:
93
93
 
94
94
  :return: default mode name
95
95
  """
96
- for id in self.items:
97
- if self.items[id].default:
98
- return id
96
+ return next((k for k, v in self.items.items() if v.default), None)
99
97
 
100
98
  def get_next(self, mode: str) -> str:
101
99
  """
@@ -104,12 +102,9 @@ class Modes:
104
102
  :param mode: current mode
105
103
  :return: next mode
106
104
  """
107
- modes = self.get_all()
108
- keys = list(modes.keys())
105
+ keys = tuple(self.items)
109
106
  idx = keys.index(mode)
110
- if idx + 1 < len(keys):
111
- return keys[idx + 1]
112
- return keys[0]
107
+ return keys[(idx + 1) % len(keys)]
113
108
 
114
109
  def get_prev(self, mode: str) -> str:
115
110
  """
@@ -118,12 +113,9 @@ class Modes:
118
113
  :param mode: current mode
119
114
  :return: previous mode
120
115
  """
121
- modes = self.get_all()
122
- keys = list(modes.keys())
116
+ keys = tuple(self.items)
123
117
  idx = keys.index(mode)
124
- if idx - 1 >= 0:
125
- return keys[idx - 1]
126
- return keys[-1]
118
+ return keys[(idx - 1) % len(keys)]
127
119
 
128
120
  def load(self):
129
121
  """Load modes"""
@@ -131,4 +123,4 @@ class Modes:
131
123
 
132
124
  def save(self):
133
125
  """Save modes"""
134
- self.provider.save(self.items)
126
+ self.provider.save(self.items)