pygpt-net 2.5.93__py3-none-any.whl → 2.5.95__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 (68) hide show
  1. pygpt_net/CHANGELOG.txt +15 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +6 -2
  4. pygpt_net/controller/chat/image.py +0 -3
  5. pygpt_net/controller/chat/response.py +5 -1
  6. pygpt_net/controller/chat/text.py +3 -1
  7. pygpt_net/controller/config/field/slider.py +9 -5
  8. pygpt_net/controller/ctx/ctx.py +23 -18
  9. pygpt_net/controller/dialogs/confirm.py +4 -1
  10. pygpt_net/controller/presets/editor.py +117 -1
  11. pygpt_net/controller/settings/editor.py +0 -6
  12. pygpt_net/controller/ui/mode.py +1 -3
  13. pygpt_net/controller/ui/tabs.py +60 -42
  14. pygpt_net/core/ctx/ctx.py +4 -2
  15. pygpt_net/core/models/models.py +6 -1
  16. pygpt_net/core/presets/presets.py +12 -1
  17. pygpt_net/core/prompt/prompt.py +10 -1
  18. pygpt_net/core/render/web/body.py +80 -23
  19. pygpt_net/core/render/web/helpers.py +9 -2
  20. pygpt_net/core/render/web/renderer.py +44 -3
  21. pygpt_net/core/tabs/tabs.py +40 -5
  22. pygpt_net/core/text/text.py +31 -2
  23. pygpt_net/core/types/openai.py +14 -1
  24. pygpt_net/data/config/config.json +6 -4
  25. pygpt_net/data/config/models.json +203 -532
  26. pygpt_net/data/config/presets/agent_openai_b2b.json +54 -0
  27. pygpt_net/data/config/settings.json +44 -14
  28. pygpt_net/data/config/settings_section.json +3 -0
  29. pygpt_net/data/css/web-blocks.css +27 -2
  30. pygpt_net/data/css/web-blocks.dark.css +7 -0
  31. pygpt_net/data/css/web-blocks.light.css +7 -0
  32. pygpt_net/data/css/web-chatgpt.css +33 -2
  33. pygpt_net/data/css/web-chatgpt.dark.css +7 -0
  34. pygpt_net/data/css/web-chatgpt.light.css +7 -0
  35. pygpt_net/data/css/web-chatgpt_wide.css +33 -2
  36. pygpt_net/data/css/web-chatgpt_wide.dark.css +7 -0
  37. pygpt_net/data/css/web-chatgpt_wide.light.css +7 -0
  38. pygpt_net/data/locale/locale.de.ini +33 -0
  39. pygpt_net/data/locale/locale.en.ini +35 -0
  40. pygpt_net/data/locale/locale.es.ini +33 -0
  41. pygpt_net/data/locale/locale.fr.ini +33 -0
  42. pygpt_net/data/locale/locale.it.ini +33 -0
  43. pygpt_net/data/locale/locale.pl.ini +35 -1
  44. pygpt_net/data/locale/locale.uk.ini +33 -0
  45. pygpt_net/data/locale/locale.zh.ini +33 -0
  46. pygpt_net/item/model.py +6 -14
  47. pygpt_net/item/preset.py +9 -1
  48. pygpt_net/provider/agents/openai/agent_b2b.py +396 -0
  49. pygpt_net/provider/core/config/patch.py +39 -1
  50. pygpt_net/provider/core/model/patch.py +16 -1
  51. pygpt_net/provider/core/preset/json_file.py +6 -0
  52. pygpt_net/provider/core/preset/patch.py +17 -0
  53. pygpt_net/provider/llms/hugging_face_router.py +132 -0
  54. pygpt_net/tools/code_interpreter/ui/html.py +2 -2
  55. pygpt_net/tools/translator/tool.py +9 -2
  56. pygpt_net/tools/translator/ui/widgets.py +45 -3
  57. pygpt_net/ui/base/config_dialog.py +1 -1
  58. pygpt_net/ui/dialog/image.py +0 -6
  59. pygpt_net/ui/dialog/preset.py +126 -8
  60. pygpt_net/ui/widget/option/combo.py +2 -0
  61. pygpt_net/ui/widget/textarea/html.py +2 -2
  62. pygpt_net/ui/widget/textarea/search_input.py +68 -2
  63. pygpt_net/ui/widget/textarea/web.py +3 -2
  64. {pygpt_net-2.5.93.dist-info → pygpt_net-2.5.95.dist-info}/METADATA +48 -26
  65. {pygpt_net-2.5.93.dist-info → pygpt_net-2.5.95.dist-info}/RECORD +68 -65
  66. {pygpt_net-2.5.93.dist-info → pygpt_net-2.5.95.dist-info}/LICENSE +0 -0
  67. {pygpt_net-2.5.93.dist-info → pygpt_net-2.5.95.dist-info}/WHEEL +0 -0
  68. {pygpt_net-2.5.93.dist-info → pygpt_net-2.5.95.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,18 @@
1
+ 2.5.95 (2025-08-09)
2
+
3
+ - Added user info personalization in Config -> Personalization, where you can provide information about yourself to the model.
4
+ - Added presets personalization with configurable AI names and avatars.
5
+ - Added a search field in the Translator tool.
6
+ - Fixed <> tags replacement in code blocks.
7
+
8
+ 2.5.94 (2025-08-09)
9
+
10
+ - Added a new LLM provider: HuggingFace Router.
11
+ - Introduced a new model: gpt-oss (OpenAI open-source model available in HuggingFace and Ollama).
12
+ - Added a new agent mode in OpenAI Agents: Bot 2 Bot.
13
+ - Fixed: Storing the last used context ID when empty.
14
+ - Fixed: Reloading items when an agent run is stopped.
15
+
1
16
  2.5.93 (2025-08-08)
2
17
 
3
18
  - Added a new tool: Translate - in menu Tools - feature #123.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
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 00:00:00 #
9
+ # Updated Date: 2025.08.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.5.93"
17
- __build__ = "2025-08-08"
16
+ __version__ = "2.5.95"
17
+ __build__ = "2025-08-09"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/app.py CHANGED
@@ -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 05:00:00 #
9
+ # Updated Date: 2025.08.09 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -16,7 +16,7 @@ import platform
16
16
 
17
17
  # disable warnings
18
18
  os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
19
- os.environ["QT_LOGGING_RULES"] = "qt.multimedia.ffmpeg=false"
19
+ os.environ["QT_LOGGING_RULES"] = "qt.multimedia.ffmpeg=false;qt.qpa.fonts=false"
20
20
 
21
21
  if platform.system() == 'Windows':
22
22
  # fix ffmpeg bug: [SWR] Output channel layout "" is invalid or unsupported.
@@ -85,6 +85,7 @@ from pygpt_net.provider.agents.openai.agent_with_feedback import Agent as OpenAI
85
85
  from pygpt_net.provider.agents.openai.bot_researcher import Agent as OpenAIAgentBotResearcher
86
86
  from pygpt_net.provider.agents.openai.agent_planner import Agent as OpenAIAgentPlanner
87
87
  from pygpt_net.provider.agents.openai.evolve import Agent as OpenAIAgentsEvolve
88
+ from pygpt_net.provider.agents.openai.agent_b2b import Agent as OpenAIAgentsB2B
88
89
 
89
90
  # LLM wrapper providers (langchain, llama-index, embeddings)
90
91
  from pygpt_net.provider.llms.anthropic import AnthropicLLM
@@ -93,6 +94,7 @@ from pygpt_net.provider.llms.deepseek_api import DeepseekApiLLM
93
94
  from pygpt_net.provider.llms.google import GoogleLLM
94
95
  # from pygpt_net.provider.llms.hugging_face import HuggingFaceLLM
95
96
  from pygpt_net.provider.llms.hugging_face_api import HuggingFaceApiLLM
97
+ from pygpt_net.provider.llms.hugging_face_router import HuggingFaceRouterLLM
96
98
  from pygpt_net.provider.llms.local import LocalLLM
97
99
  from pygpt_net.provider.llms.mistral import MistralAILLM
98
100
  from pygpt_net.provider.llms.ollama import OllamaLLM
@@ -390,6 +392,7 @@ def run(**kwargs):
390
392
  launcher.add_llm(GoogleLLM())
391
393
  # launcher.add_llm(HuggingFaceLLM())
392
394
  launcher.add_llm(HuggingFaceApiLLM())
395
+ launcher.add_llm(HuggingFaceRouterLLM())
393
396
  launcher.add_llm(LocalLLM())
394
397
  launcher.add_llm(MistralAILLM())
395
398
  launcher.add_llm(OllamaLLM())
@@ -430,6 +433,7 @@ def run(**kwargs):
430
433
  launcher.add_agent(OpenAIAgentBotResearcher()) # openai-agents
431
434
  launcher.add_agent(OpenAIAgentsExpertsFeedback()) # openai-agents
432
435
  launcher.add_agent(OpenAIAgentsEvolve()) # openai-agents
436
+ launcher.add_agent(OpenAIAgentsB2B()) # openai-agents
433
437
 
434
438
  # register custom agents
435
439
  agents = kwargs.get('agents', None)
@@ -149,9 +149,6 @@ class Image:
149
149
  string += "[{}]({})".format(basename, path) + "\n"
150
150
  i += 1
151
151
 
152
- if self.window.core.config.get('img_dialog_open'):
153
- self.window.tools.get("viewer").open_images(paths) # use viewer tool
154
-
155
152
  if not self.window.core.config.get('img_raw'):
156
153
  string += "\nPrompt: "
157
154
  string += prompt
@@ -19,7 +19,7 @@ from pygpt_net.core.types import (
19
19
  MODE_CHAT,
20
20
  )
21
21
  from pygpt_net.core.bridge.context import BridgeContext
22
- from pygpt_net.core.events import RenderEvent, KernelEvent
22
+ from pygpt_net.core.events import RenderEvent, KernelEvent, AppEvent
23
23
  from pygpt_net.item.ctx import CtxItem
24
24
  from pygpt_net.utils import trans
25
25
 
@@ -172,6 +172,10 @@ class Response:
172
172
  ctx.msg_id = None
173
173
  self.window.core.ctx.add(ctx) # store context to prevent current output from being lost
174
174
  self.window.controller.ctx.prepare_name(ctx) # summarize if not yet
175
+
176
+ # finish render
177
+ self.window.dispatch(AppEvent(AppEvent.CTX_END)) # app event
178
+ self.window.dispatch(RenderEvent(RenderEvent.RELOAD)) # reload chat window
175
179
  return
176
180
 
177
181
  # at first, handle previous context (user input) if not handled yet
@@ -102,8 +102,10 @@ class Text:
102
102
  stream_mode = False
103
103
 
104
104
  # create ctx item
105
+ meta = self.window.core.ctx.get_current_meta()
106
+ meta.preset = self.window.core.config.get('preset') # current preset
105
107
  ctx = CtxItem()
106
- ctx.meta = self.window.core.ctx.get_current_meta() # CtxMeta (owner object)
108
+ ctx.meta = meta # CtxMeta (owner object)
107
109
  ctx.internal = internal
108
110
  ctx.current = True # mark as current context item
109
111
  ctx.mode = mode # store current selected mode (not inline changed)
@@ -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.08 21:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Optional, Dict, Union
@@ -65,7 +65,8 @@ class Slider:
65
65
  value = option["max"]
66
66
 
67
67
  # update connected input field
68
- self.window.ui.config[parent_id][key].input.setText(str(value))
68
+ if parent_id in self.window.ui.config and key in self.window.ui.config[parent_id]:
69
+ self.window.ui.config[parent_id][key].input.setText(str(value))
69
70
 
70
71
  slider_value = round(float(value) * multiplier, 0)
71
72
 
@@ -83,12 +84,15 @@ class Slider:
83
84
  slider_value = option["min"] * multiplier
84
85
  elif "max" in option and slider_value > option["max"] * multiplier:
85
86
  slider_value = option["max"] * multiplier
86
- self.window.ui.config[parent_id][key].slider.setValue(slider_value)
87
+
88
+ if parent_id in self.window.ui.config and key in self.window.ui.config[parent_id]:
89
+ self.window.ui.config[parent_id][key].slider.setValue(slider_value)
87
90
 
88
91
  # from value
89
92
  else:
90
- self.window.ui.config[parent_id][key].input.setText(str(value))
91
- self.window.ui.config[parent_id][key].slider.setValue(slider_value)
93
+ if parent_id in self.window.ui.config and key in self.window.ui.config[parent_id]:
94
+ self.window.ui.config[parent_id][key].input.setText(str(value))
95
+ self.window.ui.config[parent_id][key].slider.setValue(slider_value)
92
96
 
93
97
  def on_update(
94
98
  self,
@@ -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.07 03:00:00 #
9
+ # Updated Date: 2025.08.08 23:00:00 #
10
10
  # ================================================== #
11
11
  import gc
12
12
  from typing import Optional, List
@@ -15,7 +15,7 @@ from PySide6.QtCore import QModelIndex, QTimer
15
15
  from PySide6.QtGui import QStandardItem
16
16
 
17
17
  from pygpt_net.core.events import Event, AppEvent, RenderEvent
18
- from pygpt_net.item.ctx import CtxItem
18
+ from pygpt_net.item.ctx import CtxItem, CtxMeta
19
19
 
20
20
  from .common import Common
21
21
  from .summarizer import Summarizer
@@ -283,12 +283,7 @@ class Ctx:
283
283
  self.window.core.config.set('assistant_thread', None) # reset assistant thread id
284
284
  self.update()
285
285
 
286
- # render reset
287
- data = {
288
- "meta": meta,
289
- }
290
- event = RenderEvent(RenderEvent.FRESH, data)
291
- self.window.dispatch(event)
286
+ self.fresh_output(meta) # render reset
292
287
 
293
288
  if not force: # only if real click on new context button
294
289
  self.window.controller.chat.common.unlock_input()
@@ -407,16 +402,7 @@ class Ctx:
407
402
 
408
403
  # reset appended data / prepare new ctx
409
404
  if meta is not None:
410
- data = {
411
- "meta": meta,
412
- }
413
- event = RenderEvent(RenderEvent.FRESH, data)
414
- self.window.dispatch(event)
415
- data = {
416
- "meta": meta,
417
- }
418
- event = RenderEvent(RenderEvent.ON_LOAD, data)
419
- self.window.dispatch(event)
405
+ self.fresh_output(meta) # render reset
420
406
 
421
407
  self.reload_config()
422
408
 
@@ -1244,3 +1230,22 @@ class Ctx:
1244
1230
  def clear_selected(self):
1245
1231
  """Clear selected list"""
1246
1232
  self.selected = []
1233
+
1234
+
1235
+ def fresh_output(self, meta: CtxMeta):
1236
+ """
1237
+ Fresh output for new context
1238
+
1239
+ :param meta: CtxItem
1240
+ """
1241
+ # render reset
1242
+ data = {
1243
+ "meta": meta,
1244
+ }
1245
+ event = RenderEvent(RenderEvent.FRESH, data)
1246
+ self.window.dispatch(event)
1247
+ data = {
1248
+ "meta": meta,
1249
+ }
1250
+ event = RenderEvent(RenderEvent.ON_LOAD, data)
1251
+ self.window.dispatch(event)
@@ -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 05:00:00 #
9
+ # Updated Date: 2025.08.09 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Optional
@@ -94,6 +94,9 @@ class Confirm:
94
94
  elif type == 'translator.clear.right':
95
95
  self.window.tools.get("translator").clear_right(force=True)
96
96
 
97
+ elif type == "preset.avatar.delete":
98
+ self.window.controller.presets.editor.remove_avatar(True)
99
+
97
100
  # audio transcribe
98
101
  elif type == 'audio.transcribe':
99
102
  self.window.tools.get("transcriber").transcribe(id, force=True)
@@ -6,11 +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.01 03:00:00 #
9
+ # Updated Date: 2025.08.09 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
13
13
  import os
14
+ import shutil
14
15
  from typing import Any, Optional, Dict
15
16
 
16
17
  from PySide6.QtWidgets import QVBoxLayout, QWidget, QHBoxLayout
@@ -48,6 +49,7 @@ class Editor:
48
49
  self.built = False
49
50
  self.tab_options_idx = {}
50
51
  self.opened = False
52
+ self.tmp_avatar = None
51
53
  self.options = {
52
54
  "filename": {
53
55
  "type": "text",
@@ -61,6 +63,15 @@ class Editor:
61
63
  "type": "text",
62
64
  "label": "preset.ai_name",
63
65
  },
66
+ "ai_avatar": {
67
+ "type": "text",
68
+ "label": "preset.ai_avatar",
69
+ },
70
+ "ai_personalize": {
71
+ "type": "bool",
72
+ "label": "preset.ai_personalize",
73
+ "description": "preset.ai_personalize.desc",
74
+ },
64
75
  "user_name": {
65
76
  "type": "text",
66
77
  "label": "preset.user_name",
@@ -716,6 +727,9 @@ class Editor:
716
727
  # update experts list, after ID loaded
717
728
  self.experts.update_list()
718
729
 
730
+ # setup avatar config
731
+ self.update_avatar_config(data)
732
+
719
733
  # restore functions
720
734
  if data.has_functions():
721
735
  functions = data.get_functions()
@@ -836,6 +850,14 @@ class Editor:
836
850
  # assign data from fields to preset object in items
837
851
  self.assign_data(id)
838
852
 
853
+ if is_new:
854
+ # assign tmp avatar
855
+ if self.tmp_avatar is not None:
856
+ self.window.core.presets.items[id].ai_avatar = self.tmp_avatar
857
+ self.tmp_avatar = None
858
+ else:
859
+ self.tmp_avatar = None
860
+
839
861
  # if agent, assign experts and select only agent mode
840
862
  curr_mode = self.window.core.config.get('mode')
841
863
  if curr_mode == MODE_AGENT:
@@ -956,6 +978,9 @@ class Editor:
956
978
  # extra options
957
979
  self.append_extra_options(preset)
958
980
 
981
+ # avatar update
982
+ self.update_avatar_config(preset)
983
+
959
984
  def to_current(self, preset: PresetItem):
960
985
  """
961
986
  Update preset field from editor
@@ -1018,3 +1043,94 @@ class Editor:
1018
1043
  preset.ai_name = value
1019
1044
  self.window.core.config.set('ai_name', preset.ai_name)
1020
1045
  self.window.core.presets.save(preset_id)
1046
+
1047
+ def upload_avatar(self, file_path: str):
1048
+ """
1049
+ Update avatar config for preset
1050
+
1051
+ :param file_path: path to the avatar file
1052
+ """
1053
+ preset = self.window.core.presets.get_by_uuid(self.current)
1054
+ presets_dir = self.window.core.config.get_user_dir("presets")
1055
+ avatars_dir = os.path.join(presets_dir, "avatars")
1056
+ preset_name = "_" if preset is None else preset.filename
1057
+ if not os.path.exists(avatars_dir):
1058
+ os.makedirs(avatars_dir, exist_ok=True)
1059
+ file_ext = os.path.splitext(file_path)[1]
1060
+ store_name = preset_name + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + file_ext
1061
+ avatar_path = os.path.join(avatars_dir, store_name)
1062
+
1063
+ # copy avatar to avatars directory
1064
+ if os.path.exists(avatar_path):
1065
+ os.remove(avatar_path)
1066
+ if os.path.exists(file_path):
1067
+ shutil.copy(file_path, avatar_path)
1068
+ if preset:
1069
+ preset.ai_avatar = store_name
1070
+ else:
1071
+ self.tmp_avatar = store_name
1072
+ self.window.controller.config.apply_value(
1073
+ parent_id=self.id,
1074
+ key="ai_avatar",
1075
+ option=self.options["ai_avatar"],
1076
+ value=store_name,
1077
+ )
1078
+ self.window.ui.nodes['preset.editor.avatar'].load_avatar(avatar_path)
1079
+ self.window.ui.nodes['preset.editor.avatar'].enable_remove_button(True)
1080
+ return avatar_path
1081
+
1082
+ def update_avatar_config(self, preset: PresetItem):
1083
+ """
1084
+ Update avatar config for preset
1085
+
1086
+ :param preset: preset item
1087
+ """
1088
+ avatar_path = preset.ai_avatar
1089
+ if avatar_path:
1090
+ file_path = os.path.join(
1091
+ self.window.core.config.get_user_dir("presets"),
1092
+ "avatars",
1093
+ avatar_path,
1094
+ )
1095
+ if not os.path.exists(file_path):
1096
+ self.window.ui.nodes['preset.editor.avatar'].remove_avatar()
1097
+ print("Avatar file does not exist:", file_path)
1098
+ return
1099
+ self.window.ui.nodes['preset.editor.avatar'].load_avatar(file_path)
1100
+ self.window.ui.nodes['preset.editor.avatar'].enable_remove_button(True)
1101
+ else:
1102
+ self.window.ui.nodes['preset.editor.avatar'].remove_avatar()
1103
+
1104
+ def remove_avatar(self, force: bool = False):
1105
+ """
1106
+ Remove avatar from preset editor
1107
+
1108
+ :param force: force remove avatar
1109
+ """
1110
+ if not force:
1111
+ self.window.ui.dialogs.confirm(
1112
+ type='preset.avatar.delete',
1113
+ id="",
1114
+ msg=trans('confirm.preset.avatar.delete'),
1115
+ )
1116
+ return
1117
+ preset = self.window.core.presets.get_by_uuid(self.current)
1118
+ if preset:
1119
+ current = preset.ai_avatar
1120
+ if current:
1121
+ presets_dir = self.window.core.config.get_user_dir("presets")
1122
+ avatars_dir = os.path.join(presets_dir, "avatars")
1123
+ avatar_path = os.path.join(avatars_dir, current)
1124
+ if os.path.exists(avatar_path):
1125
+ os.remove(avatar_path)
1126
+ preset.ai_avatar = ""
1127
+ else:
1128
+ self.tmp_avatar = None
1129
+
1130
+ self.window.ui.nodes['preset.editor.avatar'].remove_avatar()
1131
+ self.window.controller.config.apply_value(
1132
+ parent_id=self.id,
1133
+ key="ai_avatar",
1134
+ option=self.options["ai_avatar"],
1135
+ value="",
1136
+ )
@@ -60,7 +60,6 @@ class Editor:
60
60
  self.window.ui.add_hook("update.config.ctx.records.folders.top", self.hook_update)
61
61
  self.window.ui.add_hook("update.config.layout.density", self.hook_update)
62
62
  self.window.ui.add_hook("update.config.layout.tooltips", self.hook_update)
63
- self.window.ui.add_hook("update.config.img_dialog_open", self.hook_update)
64
63
  self.window.ui.add_hook("update.config.access.voice_control", self.hook_update)
65
64
  self.window.ui.add_hook("update.config.debug", self.hook_update)
66
65
  self.window.ui.add_hook("update.config.notepad.num", self.hook_update)
@@ -327,11 +326,6 @@ class Editor:
327
326
  self.window.controller.theme.reload()
328
327
  self.window.controller.theme.menu.update_density()
329
328
 
330
- # toggle image dialog auto-open
331
- elif key == "img_dialog_open":
332
- self.window.core.config.set(key, value)
333
- self.window.ui.nodes['dialog.image.open.toggle'].setChecked(value)
334
-
335
329
  # debug: menu
336
330
  elif key == "debug":
337
331
  self.window.core.config.set(key, value)
@@ -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.01 03:00:00 #
9
+ # Updated Date: 2025.08.09 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.types import (
@@ -82,10 +82,8 @@ class Mode:
82
82
  self.window.ui.nodes['preset.editor.remote_tools'].setVisible(False)
83
83
 
84
84
  if mode == MODE_COMPLETION:
85
- self.window.ui.nodes['preset.editor.ai_name'].setVisible(True)
86
85
  self.window.ui.nodes['preset.editor.user_name'].setVisible(True)
87
86
  else:
88
- self.window.ui.nodes['preset.editor.ai_name'].setVisible(False)
89
87
  self.window.ui.nodes['preset.editor.user_name'].setVisible(False)
90
88
 
91
89
  if mode == MODE_AGENT_OPENAI:
@@ -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.07 18:00:00 #
9
+ # Updated Date: 2025.08.08 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Optional, Tuple
@@ -614,7 +614,7 @@ class Tabs:
614
614
  :param name: new title
615
615
  :param close: close dialog
616
616
  """
617
- self.window.core.tabs.update_title(idx, name)
617
+ self.window.core.tabs.update_title(idx, name, name)
618
618
  if close:
619
619
  self.window.ui.dialog['rename'].close()
620
620
  self.debug()
@@ -861,52 +861,70 @@ class Tabs:
861
861
  :param title: new tab name (optional, for chat tab)
862
862
  :param meta: context meta (optional, for chat tab)
863
863
  """
864
- # first try to focus current tab
864
+ # try to focus tab
865
865
  if self.get_current_type() != type:
866
- # first, check in second column
867
- second_column_idx = 1 if self.column_idx == 0 else 0
868
- # get current tab from second column
869
- tabs = self.window.ui.layout.get_tabs_by_idx(second_column_idx)
870
- second_tabs_idx = tabs.currentIndex()
871
- second_tab = self.window.core.tabs.get_tab_by_index(second_tabs_idx, second_column_idx)
872
- if second_tab is not None and second_tab.type == type:
873
- # switch to second column
874
- self.on_column_focus(second_column_idx)
875
- tabs.setCurrentIndex(second_tabs_idx)
876
- if meta:
877
- QTimer.singleShot(100, lambda: self.window.controller.ctx.load(meta.id))
878
- self.debug()
879
- return
880
866
 
881
- idx, column_idx, exists = self.window.core.tabs.get_min_idx_by_type_exists(type)
867
+ # find the closest tab in current column (on left side)
868
+ current = self.get_current_tab()
869
+ exists = False
870
+ if current:
871
+ idx, column_idx, exists = self.window.core.tabs.get_closest_idx_by_type_exists(
872
+ current,
873
+ type,
874
+ self.column_idx
875
+ )
882
876
  if exists:
883
- tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
884
- if tabs and idx:
885
- tabs.setCurrentIndex(idx)
886
- self.debug()
887
- return
877
+ tab = self.window.core.tabs.get_tab_by_index(idx, column_idx)
888
878
  else:
889
- # if current is not type, find first tab
879
+ # if not exists in current col, then find first idx in any column
890
880
  tab = self.window.core.tabs.get_first_by_type(type)
891
- if tab:
892
- tabs = self.window.ui.layout.get_tabs_by_idx(tab.column_idx)
893
- if tabs:
894
- idx = tab.idx
895
- if data_id is not None:
896
- tab.data_id = data_id
897
- if title is not None:
898
- self.update_title_current(title)
899
- else:
900
- self.on_column_focus(tab.column_idx)
901
- if meta is not None:
902
- self.on_column_focus(tab.column_idx)
903
- self.window.controller.ctx.load(meta.id)
904
- QTimer.singleShot(100, lambda: self.window.controller.ctx.load(meta.id))
905
- self.on_column_focus(tab.column_idx)
906
- tabs.setCurrentIndex(idx)
881
+
882
+ if tab:
883
+ # if tab is found in current column, switch to it
884
+ tabs = self.window.ui.layout.get_tabs_by_idx(tab.column_idx)
885
+ if tabs:
886
+ idx = tab.idx
887
+ if data_id is not None:
888
+ tab.data_id = data_id
889
+ if title is not None:
890
+ self.update_title_current(title)
891
+ else:
892
+ self.on_column_focus(tab.column_idx)
893
+ if meta is not None:
894
+ self.on_column_focus(tab.column_idx)
895
+ self.window.controller.ctx.load(meta.id)
896
+ QTimer.singleShot(100, lambda: self.window.controller.ctx.load(meta.id))
897
+ self.on_column_focus(tab.column_idx)
898
+ tabs.setCurrentIndex(idx)
899
+ else:
900
+ # if not found in current column, then check in second column
901
+ second_column_idx = 1 if self.column_idx == 0 else 0
902
+ # get current tab from second column
903
+ tabs = self.window.ui.layout.get_tabs_by_idx(second_column_idx)
904
+ second_tabs_idx = tabs.currentIndex()
905
+ second_tab = self.window.core.tabs.get_tab_by_index(second_tabs_idx, second_column_idx)
906
+ if second_tab is not None and second_tab.type == type:
907
+ # switch to second column
908
+ self.on_column_focus(second_column_idx)
909
+ tabs.setCurrentIndex(second_tabs_idx)
910
+ if meta:
911
+ QTimer.singleShot(100, lambda: self.window.controller.ctx.load(meta.id))
912
+
913
+ # if second and split screen disabled, then enable it
914
+ if tab and tab.column_idx == 1:
915
+ if not self.is_split_screen_enabled():
916
+ self.enable_split_screen(update_switch=True)
907
917
 
908
918
  self.debug()
909
919
 
920
+ def is_split_screen_enabled(self) -> bool:
921
+ """
922
+ Check if split screen mode is enabled
923
+
924
+ :return: True if split screen is enabled, False otherwise
925
+ """
926
+ return self.window.core.config.get("layout.split", False)
927
+
910
928
 
911
929
  def on_split_screen_changed(self, state: bool):
912
930
  """
@@ -914,7 +932,7 @@ class Tabs:
914
932
 
915
933
  :param state: True if split screen is enabled
916
934
  """
917
- prev_state = self.window.core.config.get("layout.split", False)
935
+ prev_state = self.is_split_screen_enabled()
918
936
  self.window.core.config.set("layout.split", state)
919
937
  if prev_state != state:
920
938
  if self.window.ui.nodes['layout.split'].box.isChecked() != state:
@@ -927,7 +945,7 @@ class Tabs:
927
945
 
928
946
  :param update_switch: True if switch should be updated
929
947
  """
930
- if self.window.core.config.get("layout.split", False):
948
+ if self.is_split_screen_enabled():
931
949
  return
932
950
 
933
951
  self.window.ui.splitters['columns'].setSizes([1, 1])
pygpt_net/core/ctx/ctx.py CHANGED
@@ -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.07 02:00:00 #
9
+ # Updated Date: 2025.08.09 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -429,6 +429,8 @@ class Ctx:
429
429
  if meta is None:
430
430
  self.window.core.debug.log("Error creating new ctx")
431
431
  return
432
+ preset = self.window.core.config.get('preset')
433
+ meta.preset = preset
432
434
  self.meta[meta.id] = meta
433
435
  self.tmp_meta = meta
434
436
  self.current = meta.id
@@ -436,7 +438,7 @@ class Ctx:
436
438
  self.assistant = None
437
439
  self.mode = self.window.core.config.get('mode')
438
440
  self.model = self.window.core.config.get('model')
439
- self.preset = self.window.core.config.get('preset')
441
+ self.preset = preset
440
442
  self.clear_items()
441
443
  self.save(meta.id)
442
444
 
@@ -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.19 00:00:00 #
9
+ # Updated Date: 2025.08.08 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -527,6 +527,11 @@ class Models:
527
527
  args["api_key"] = self.window.core.config.get('api_key_mistral', "")
528
528
  args["base_url"] = self.window.core.config.get('api_endpoint_mistral', "")
529
529
  self.window.core.debug.info("[api] Using client: Mistral AI API")
530
+ # HuggingFace Router
531
+ 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', "")
534
+ self.window.core.debug.info("[api] Using client: HuggingFace Router API")
530
535
  else:
531
536
  self.window.core.debug.info("[api] Using client: OpenAI (default)")
532
537