pygpt-net 2.6.20__py3-none-any.whl → 2.6.22__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 (221) hide show
  1. pygpt_net/CHANGELOG.txt +13 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +3 -1
  4. pygpt_net/controller/__init__.py +4 -8
  5. pygpt_net/controller/access/voice.py +2 -2
  6. pygpt_net/controller/agent/agent.py +130 -2
  7. pygpt_net/controller/agent/experts.py +93 -96
  8. pygpt_net/controller/agent/llama.py +2 -1
  9. pygpt_net/controller/assistant/assistant.py +18 -1
  10. pygpt_net/controller/assistant/batch.py +2 -3
  11. pygpt_net/controller/assistant/editor.py +2 -2
  12. pygpt_net/controller/assistant/files.py +2 -3
  13. pygpt_net/controller/assistant/store.py +2 -2
  14. pygpt_net/controller/attachment/attachment.py +17 -1
  15. pygpt_net/controller/audio/audio.py +2 -2
  16. pygpt_net/controller/camera/camera.py +15 -7
  17. pygpt_net/controller/chat/chat.py +2 -2
  18. pygpt_net/controller/chat/common.py +50 -33
  19. pygpt_net/controller/chat/image.py +67 -77
  20. pygpt_net/controller/chat/input.py +94 -166
  21. pygpt_net/controller/chat/output.py +83 -140
  22. pygpt_net/controller/chat/response.py +83 -102
  23. pygpt_net/controller/chat/text.py +116 -149
  24. pygpt_net/controller/ctx/common.py +2 -1
  25. pygpt_net/controller/ctx/ctx.py +87 -6
  26. pygpt_net/controller/files/files.py +13 -1
  27. pygpt_net/controller/idx/idx.py +26 -2
  28. pygpt_net/controller/idx/indexer.py +85 -76
  29. pygpt_net/controller/kernel/reply.py +53 -66
  30. pygpt_net/controller/kernel/stack.py +16 -16
  31. pygpt_net/controller/lang/lang.py +52 -34
  32. pygpt_net/controller/model/importer.py +3 -2
  33. pygpt_net/controller/model/model.py +62 -3
  34. pygpt_net/controller/notepad/notepad.py +86 -84
  35. pygpt_net/controller/plugins/settings.py +3 -4
  36. pygpt_net/controller/settings/editor.py +4 -4
  37. pygpt_net/controller/settings/profile.py +105 -124
  38. pygpt_net/controller/theme/menu.py +154 -57
  39. pygpt_net/controller/theme/nodes.py +51 -44
  40. pygpt_net/controller/theme/theme.py +33 -9
  41. pygpt_net/controller/tools/tools.py +2 -2
  42. pygpt_net/controller/ui/tabs.py +2 -3
  43. pygpt_net/controller/ui/ui.py +16 -2
  44. pygpt_net/core/agents/observer/evaluation.py +3 -3
  45. pygpt_net/core/agents/provider.py +25 -3
  46. pygpt_net/core/agents/runner.py +4 -1
  47. pygpt_net/core/agents/runners/llama_workflow.py +19 -7
  48. pygpt_net/core/agents/runners/loop.py +3 -1
  49. pygpt_net/core/agents/runners/openai_workflow.py +17 -3
  50. pygpt_net/core/agents/tools.py +4 -1
  51. pygpt_net/core/bridge/context.py +34 -37
  52. pygpt_net/core/ctx/container.py +13 -12
  53. pygpt_net/core/ctx/ctx.py +1 -1
  54. pygpt_net/core/ctx/output.py +7 -4
  55. pygpt_net/core/db/database.py +2 -2
  56. pygpt_net/core/debug/console/console.py +2 -2
  57. pygpt_net/core/debug/debug.py +12 -1
  58. pygpt_net/core/dispatcher/dispatcher.py +24 -1
  59. pygpt_net/core/events/app.py +7 -7
  60. pygpt_net/core/events/control.py +26 -26
  61. pygpt_net/core/events/event.py +6 -3
  62. pygpt_net/core/events/kernel.py +2 -2
  63. pygpt_net/core/events/render.py +13 -13
  64. pygpt_net/core/experts/experts.py +76 -82
  65. pygpt_net/core/experts/worker.py +12 -12
  66. pygpt_net/core/filesystem/actions.py +1 -2
  67. pygpt_net/core/models/models.py +5 -1
  68. pygpt_net/core/models/ollama.py +14 -5
  69. pygpt_net/core/render/plain/helpers.py +2 -5
  70. pygpt_net/core/render/plain/renderer.py +26 -30
  71. pygpt_net/core/render/web/body.py +1 -1
  72. pygpt_net/core/render/web/helpers.py +2 -2
  73. pygpt_net/core/render/web/renderer.py +4 -4
  74. pygpt_net/core/settings/settings.py +43 -13
  75. pygpt_net/core/tabs/tabs.py +20 -13
  76. pygpt_net/core/types/__init__.py +2 -1
  77. pygpt_net/core/types/agent.py +4 -4
  78. pygpt_net/core/types/base.py +19 -0
  79. pygpt_net/core/types/console.py +6 -6
  80. pygpt_net/core/types/mode.py +8 -8
  81. pygpt_net/core/types/multimodal.py +3 -3
  82. pygpt_net/core/types/openai.py +2 -1
  83. pygpt_net/data/config/config.json +5 -5
  84. pygpt_net/data/config/models.json +19 -3
  85. pygpt_net/data/config/settings.json +14 -14
  86. pygpt_net/data/locale/locale.de.ini +4 -1
  87. pygpt_net/data/locale/locale.en.ini +6 -3
  88. pygpt_net/data/locale/locale.es.ini +4 -1
  89. pygpt_net/data/locale/locale.fr.ini +4 -1
  90. pygpt_net/data/locale/locale.it.ini +4 -1
  91. pygpt_net/data/locale/locale.pl.ini +5 -4
  92. pygpt_net/data/locale/locale.uk.ini +4 -1
  93. pygpt_net/data/locale/locale.zh.ini +4 -1
  94. pygpt_net/item/ctx.py +256 -240
  95. pygpt_net/item/model.py +59 -116
  96. pygpt_net/item/preset.py +122 -105
  97. pygpt_net/plugin/twitter/plugin.py +2 -2
  98. pygpt_net/provider/agents/llama_index/workflow/planner.py +3 -3
  99. pygpt_net/provider/agents/openai/agent.py +4 -12
  100. pygpt_net/provider/agents/openai/agent_b2b.py +10 -15
  101. pygpt_net/provider/agents/openai/agent_planner.py +4 -4
  102. pygpt_net/provider/agents/openai/agent_with_experts.py +3 -7
  103. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -8
  104. pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -8
  105. pygpt_net/provider/agents/openai/bot_researcher.py +2 -18
  106. pygpt_net/provider/agents/openai/bots/__init__.py +0 -0
  107. pygpt_net/provider/agents/openai/bots/research_bot/__init__.py +0 -0
  108. pygpt_net/provider/agents/openai/bots/research_bot/agents/__init__.py +0 -0
  109. pygpt_net/provider/agents/openai/bots/research_bot/agents/planner_agent.py +1 -1
  110. pygpt_net/provider/agents/openai/bots/research_bot/agents/search_agent.py +1 -0
  111. pygpt_net/provider/agents/openai/bots/research_bot/agents/writer_agent.py +1 -1
  112. pygpt_net/provider/agents/openai/bots/research_bot/manager.py +1 -10
  113. pygpt_net/provider/agents/openai/evolve.py +5 -9
  114. pygpt_net/provider/agents/openai/supervisor.py +4 -8
  115. pygpt_net/provider/core/config/patch.py +10 -3
  116. pygpt_net/provider/core/ctx/db_sqlite/utils.py +43 -43
  117. pygpt_net/provider/core/model/patch.py +11 -1
  118. pygpt_net/provider/core/preset/json_file.py +47 -49
  119. pygpt_net/provider/gpt/agents/experts.py +2 -2
  120. pygpt_net/tools/audio_transcriber/ui/dialogs.py +44 -54
  121. pygpt_net/tools/code_interpreter/body.py +1 -2
  122. pygpt_net/tools/code_interpreter/tool.py +7 -4
  123. pygpt_net/tools/code_interpreter/ui/html.py +1 -3
  124. pygpt_net/tools/code_interpreter/ui/widgets.py +2 -3
  125. pygpt_net/tools/html_canvas/ui/widgets.py +1 -3
  126. pygpt_net/tools/image_viewer/ui/dialogs.py +40 -37
  127. pygpt_net/tools/indexer/ui/widgets.py +2 -4
  128. pygpt_net/tools/media_player/tool.py +2 -5
  129. pygpt_net/tools/media_player/ui/widgets.py +60 -36
  130. pygpt_net/tools/text_editor/ui/widgets.py +18 -19
  131. pygpt_net/tools/translator/ui/widgets.py +39 -35
  132. pygpt_net/ui/base/context_menu.py +9 -4
  133. pygpt_net/ui/dialog/db.py +1 -3
  134. pygpt_net/ui/dialog/models.py +1 -3
  135. pygpt_net/ui/dialog/models_importer.py +2 -4
  136. pygpt_net/ui/dialogs.py +34 -30
  137. pygpt_net/ui/layout/chat/attachments.py +72 -84
  138. pygpt_net/ui/layout/chat/attachments_ctx.py +40 -44
  139. pygpt_net/ui/layout/chat/attachments_uploaded.py +36 -39
  140. pygpt_net/ui/layout/chat/calendar.py +100 -70
  141. pygpt_net/ui/layout/chat/chat.py +23 -17
  142. pygpt_net/ui/layout/chat/input.py +95 -118
  143. pygpt_net/ui/layout/chat/output.py +100 -162
  144. pygpt_net/ui/layout/chat/painter.py +89 -61
  145. pygpt_net/ui/layout/ctx/ctx_list.py +43 -52
  146. pygpt_net/ui/layout/status.py +23 -14
  147. pygpt_net/ui/layout/toolbox/agent.py +27 -38
  148. pygpt_net/ui/layout/toolbox/agent_llama.py +42 -45
  149. pygpt_net/ui/layout/toolbox/assistants.py +42 -38
  150. pygpt_net/ui/layout/toolbox/computer_env.py +32 -23
  151. pygpt_net/ui/layout/toolbox/footer.py +13 -16
  152. pygpt_net/ui/layout/toolbox/image.py +18 -21
  153. pygpt_net/ui/layout/toolbox/indexes.py +46 -89
  154. pygpt_net/ui/layout/toolbox/mode.py +20 -7
  155. pygpt_net/ui/layout/toolbox/model.py +12 -10
  156. pygpt_net/ui/layout/toolbox/presets.py +68 -52
  157. pygpt_net/ui/layout/toolbox/prompt.py +31 -58
  158. pygpt_net/ui/layout/toolbox/toolbox.py +25 -21
  159. pygpt_net/ui/layout/toolbox/vision.py +20 -22
  160. pygpt_net/ui/main.py +2 -4
  161. pygpt_net/ui/menu/about.py +64 -84
  162. pygpt_net/ui/menu/audio.py +87 -63
  163. pygpt_net/ui/menu/config.py +121 -127
  164. pygpt_net/ui/menu/debug.py +69 -76
  165. pygpt_net/ui/menu/file.py +32 -35
  166. pygpt_net/ui/menu/menu.py +2 -3
  167. pygpt_net/ui/menu/plugins.py +69 -33
  168. pygpt_net/ui/menu/theme.py +45 -46
  169. pygpt_net/ui/menu/tools.py +56 -60
  170. pygpt_net/ui/menu/video.py +20 -25
  171. pygpt_net/ui/tray.py +1 -2
  172. pygpt_net/ui/widget/audio/bar.py +1 -3
  173. pygpt_net/ui/widget/audio/input_button.py +3 -4
  174. pygpt_net/ui/widget/calendar/select.py +1 -2
  175. pygpt_net/ui/widget/dialog/base.py +12 -9
  176. pygpt_net/ui/widget/dialog/editor_file.py +20 -23
  177. pygpt_net/ui/widget/dialog/find.py +25 -24
  178. pygpt_net/ui/widget/dialog/profile.py +57 -53
  179. pygpt_net/ui/widget/draw/painter.py +62 -93
  180. pygpt_net/ui/widget/element/button.py +42 -30
  181. pygpt_net/ui/widget/element/checkbox.py +23 -15
  182. pygpt_net/ui/widget/element/group.py +6 -5
  183. pygpt_net/ui/widget/element/labels.py +1 -2
  184. pygpt_net/ui/widget/filesystem/explorer.py +93 -102
  185. pygpt_net/ui/widget/image/display.py +1 -2
  186. pygpt_net/ui/widget/lists/assistant.py +1 -2
  187. pygpt_net/ui/widget/lists/attachment.py +1 -2
  188. pygpt_net/ui/widget/lists/attachment_ctx.py +1 -2
  189. pygpt_net/ui/widget/lists/context.py +2 -4
  190. pygpt_net/ui/widget/lists/index.py +1 -2
  191. pygpt_net/ui/widget/lists/model.py +1 -2
  192. pygpt_net/ui/widget/lists/model_editor.py +1 -2
  193. pygpt_net/ui/widget/lists/model_importer.py +1 -2
  194. pygpt_net/ui/widget/lists/preset.py +1 -2
  195. pygpt_net/ui/widget/lists/preset_plugins.py +1 -2
  196. pygpt_net/ui/widget/lists/profile.py +1 -2
  197. pygpt_net/ui/widget/lists/uploaded.py +1 -2
  198. pygpt_net/ui/widget/option/checkbox.py +2 -4
  199. pygpt_net/ui/widget/option/checkbox_list.py +1 -4
  200. pygpt_net/ui/widget/option/cmd.py +1 -4
  201. pygpt_net/ui/widget/option/dictionary.py +25 -28
  202. pygpt_net/ui/widget/option/input.py +1 -3
  203. pygpt_net/ui/widget/tabs/Input.py +16 -12
  204. pygpt_net/ui/widget/tabs/body.py +5 -3
  205. pygpt_net/ui/widget/tabs/layout.py +36 -25
  206. pygpt_net/ui/widget/tabs/output.py +96 -74
  207. pygpt_net/ui/widget/textarea/calendar_note.py +1 -2
  208. pygpt_net/ui/widget/textarea/editor.py +41 -73
  209. pygpt_net/ui/widget/textarea/find.py +11 -10
  210. pygpt_net/ui/widget/textarea/html.py +3 -6
  211. pygpt_net/ui/widget/textarea/input.py +63 -64
  212. pygpt_net/ui/widget/textarea/notepad.py +54 -38
  213. pygpt_net/ui/widget/textarea/output.py +65 -54
  214. pygpt_net/ui/widget/textarea/search_input.py +5 -4
  215. pygpt_net/ui/widget/textarea/web.py +2 -4
  216. pygpt_net/ui/widget/vision/camera.py +2 -31
  217. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/METADATA +25 -154
  218. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/RECORD +218 -217
  219. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/LICENSE +0 -0
  220. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/WHEEL +0 -0
  221. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/entry_points.txt +0 -0
@@ -6,12 +6,13 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.20 21:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from PySide6.QtGui import QAction
12
+ from PySide6.QtGui import QAction, QActionGroup
13
13
 
14
14
  from pygpt_net.utils import trans, trans_reload
15
+
15
16
  from .custom import Custom
16
17
  from .mapping import Mapping
17
18
  from .plugins import Plugins
@@ -31,28 +32,48 @@ class Lang:
31
32
  self.plugins = Plugins(window)
32
33
  self.settings = Settings(window)
33
34
  self.loaded = False
35
+ self._lang_group = None
36
+
37
+ def _on_lang_triggered(self, action):
38
+ """
39
+ Handle language change triggered by menu action
40
+
41
+ :param action: QAction instance
42
+ """
43
+ self.window.controller.lang.toggle(action.data())
34
44
 
35
45
  def setup(self):
36
46
  """Setup language menu"""
37
- # get files from locale directory
38
47
  if not self.loaded:
39
- langs = self.window.core.config.get_available_langs()
48
+ w = self.window
49
+ menu = w.ui.menu
50
+ if self._lang_group is None:
51
+ self._lang_group = QActionGroup(w)
52
+ self._lang_group.setExclusive(True)
53
+ self._lang_group.triggered.connect(self._on_lang_triggered)
54
+
55
+ langs = w.core.config.get_available_langs()
56
+ menu_lang = menu['lang']
57
+ menu_root = menu['menu.lang']
40
58
  for lang in langs:
41
- self.window.ui.menu['lang'][lang] = QAction(lang.upper(), self.window, checkable=True)
42
- self.window.ui.menu['lang'][lang].triggered.connect(
43
- lambda checked=None,
44
- lang=lang: self.window.controller.lang.toggle(lang))
45
- self.window.ui.menu['menu.lang'].addAction(self.window.ui.menu['lang'][lang])
59
+ act = QAction(lang.upper(), w, checkable=True)
60
+ act.setData(lang)
61
+ menu_lang[lang] = act
62
+ self._lang_group.addAction(act)
63
+ menu_root.addAction(act)
46
64
  self.loaded = True
47
65
  self.update()
48
66
 
49
67
  def update(self):
50
68
  """Update language menu"""
51
- for lang in self.window.ui.menu['lang']:
52
- self.window.ui.menu['lang'][lang].setChecked(False)
53
- lang = self.window.core.config.get('lang')
54
- if lang in self.window.ui.menu['lang']:
55
- self.window.ui.menu['lang'][lang].setChecked(True)
69
+ menu_lang = self.window.ui.menu['lang']
70
+ current = self.window.core.config.get('lang')
71
+ act = menu_lang.get(current)
72
+ if act is not None:
73
+ act.setChecked(True)
74
+ else:
75
+ for a in menu_lang.values():
76
+ a.setChecked(False)
56
77
 
57
78
  def reload_config(self):
58
79
  """Reload language config"""
@@ -64,37 +85,34 @@ class Lang:
64
85
 
65
86
  :param id: language code to toggle
66
87
  """
67
- self.window.core.config.set('lang', id)
68
- self.window.core.config.save()
69
- trans('', True) # force reload locale
70
-
71
- self.update() # update menu
72
- self.mapping.apply() # nodes mapping
73
- self.custom.apply() # custom nodes
88
+ w = self.window
89
+ c = w.controller
90
+ conf = w.core.config
74
91
 
75
- # tabs
76
- self.window.controller.ui.tabs.reload_titles()
92
+ conf.set('lang', id)
93
+ conf.save()
94
+ trans('', True)
77
95
 
78
- # calendar
79
- self.window.controller.calendar.note.update_current()
96
+ self.update()
97
+ self.mapping.apply()
98
+ self.custom.apply()
80
99
 
81
- # settings
100
+ c.ui.tabs.reload_titles()
101
+ c.calendar.note.update_current()
82
102
  self.settings.apply()
83
103
 
84
- # plugins
85
104
  try:
86
105
  self.plugins.apply()
87
106
  except Exception as e:
88
107
  print("Error updating plugin locale", e)
89
- self.window.core.debug.log(e)
108
+ w.core.debug.log(e)
90
109
 
91
- # reload UI
92
- self.window.controller.ctx.common.update_label_by_current()
93
- self.window.controller.ctx.update(True, False)
94
- self.window.controller.ui.update() # update all (toolbox, etc.)
95
- self.window.update_status('') # clear status
110
+ w.controller.ctx.common.update_label_by_current()
111
+ w.controller.ctx.update(True, False)
112
+ w.controller.ui.update()
113
+ w.update_status('')
96
114
 
97
115
  def reload(self):
98
116
  """Reload language"""
99
117
  self.reload_config()
100
- self.setup()
118
+ self.setup()
@@ -6,14 +6,14 @@
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.02 20:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
13
13
  import os
14
14
  from typing import List, Dict, Optional, Any
15
15
 
16
- from qasync import QApplication
16
+ from PySide6.QtWidgets import QApplication
17
17
 
18
18
  from pygpt_net.core.events import Event
19
19
  from pygpt_net.core.types import (
@@ -475,6 +475,7 @@ class Importer:
475
475
  MODE_LLAMA_INDEX,
476
476
  MODE_AGENT,
477
477
  MODE_AGENT_LLAMA,
478
+ MODE_AGENT_OPENAI,
478
479
  MODE_EXPERT,
479
480
  ]
480
481
  m.provider = 'ollama'
@@ -6,18 +6,20 @@
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.15 03:00:00 #
9
+ # Updated Date: 2025.08.23 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ import os
12
13
  from typing import Optional
13
14
 
14
- from pygpt_net.core.events import Event, AppEvent
15
+ from pygpt_net.core.events import Event, AppEvent, BaseEvent
16
+ from pygpt_net.core.types import MODE_LLAMA_INDEX, MODE_CHAT
15
17
  from pygpt_net.item.model import ModelItem
18
+ from pygpt_net.utils import trans
16
19
 
17
20
  from .editor import Editor
18
21
  from .importer import Importer
19
22
 
20
-
21
23
  class Model:
22
24
  def __init__(self, window=None):
23
25
  """
@@ -32,6 +34,63 @@ class Model:
32
34
  def _ensure_current_model_map(self):
33
35
  return self.window.core.config.data.setdefault('current_model', {})
34
36
 
37
+ def handle(self, event: BaseEvent):
38
+ """
39
+ Handle events
40
+
41
+ :param event: BaseEvent: Event to handle
42
+ """
43
+ name = event.name
44
+ mode = self.window.core.config.get("mode")
45
+
46
+ # on input begin
47
+ if name == Event.INPUT_BEGIN:
48
+ force = event.data.get("force", False)
49
+ stop = event.data.get("stop", False)
50
+ if not force and not stop:
51
+ # check ollama model
52
+ model = self.window.core.config.get('model')
53
+ if model:
54
+ model_data = self.window.core.models.get(model)
55
+ if model_data is not None and model_data.is_ollama():
56
+ if (mode == MODE_LLAMA_INDEX or
57
+ (
58
+ mode == MODE_CHAT and not model_data.is_openai_supported() and model_data.is_ollama()
59
+ )
60
+ ):
61
+ model_id = model_data.get_ollama_model()
62
+
63
+ # load ENV vars first
64
+ if ('env' in model_data.llama_index
65
+ and model_data.llama_index['env'] is not None):
66
+ for item in model_data.llama_index['env']:
67
+ key = item.get('name', '').strip()
68
+ value = item.get('value', '').strip()
69
+ os.environ[key] = value
70
+ status = self.window.core.models.ollama.check_model(model_id)
71
+ is_installed = status.get('is_installed', False)
72
+ is_model = status.get('is_model', False)
73
+ if not is_installed:
74
+ event.data["stop"] = True # stop flow
75
+ self.window.ui.dialogs.alert(trans("dialog.ollama.not_installed"))
76
+ return
77
+ if not is_model:
78
+ event.data["stop"] = True # stop flow
79
+ self.window.ui.dialogs.alert(
80
+ trans("dialog.ollama.model_not_found").replace("{model}", model_id))
81
+ return
82
+
83
+ # on input before
84
+ elif name == Event.INPUT_BEFORE:
85
+ # check API key, show monit if no API key for current provider
86
+ model = self.window.core.config.get('model')
87
+ if model:
88
+ model_data = self.window.core.models.get(model)
89
+ if not self.window.controller.chat.common.check_api_key(mode=mode, model=model_data, monit=False):
90
+ self.window.controller.chat.common.check_api_key(mode=mode, model=model_data, monit=True)
91
+ event.data["stop"] = True # stop flow
92
+ return
93
+
35
94
  def select(self, model: str):
36
95
  """
37
96
  Select model
@@ -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.05 21:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, Tuple
@@ -20,8 +20,6 @@ from pygpt_net.ui.widget.tabs.body import TabBody
20
20
  from pygpt_net.ui.widget.textarea.notepad import NotepadWidget
21
21
  from pygpt_net.utils import trans
22
22
 
23
- import pygpt_net.icons_rc
24
-
25
23
 
26
24
  class Notepad:
27
25
  def __init__(self, window=None):
@@ -32,8 +30,7 @@ class Notepad:
32
30
  """
33
31
  self.window = window
34
32
  self.opened_once = False
35
- self.opened_idx = []
36
-
33
+ self.opened_idx = set()
37
34
 
38
35
  def get_next_suffix(self) -> int:
39
36
  """
@@ -41,13 +38,16 @@ class Notepad:
41
38
 
42
39
  :return: next notepad suffix
43
40
  """
44
- idx = self.window.core.tabs.count_by_type(Tab.TAB_NOTEPAD)
45
- for tab in self.window.core.tabs.get_tabs_by_type(Tab.TAB_NOTEPAD):
41
+ tabs_core = self.window.core.tabs
42
+ tabs = tabs_core.get_tabs_by_type(Tab.TAB_NOTEPAD)
43
+ idx = tabs_core.count_by_type(Tab.TAB_NOTEPAD)
44
+ for tab in tabs:
45
+ title = tab.title or ""
46
46
  try:
47
- num = int(tab.title.split(" ")[-1])
48
- if num >= idx:
47
+ num = int(title.rsplit(" ", 1)[-1])
48
+ if num > idx:
49
49
  idx = num
50
- except ValueError:
50
+ except (ValueError, IndexError):
51
51
  continue
52
52
  return idx + 1
53
53
 
@@ -58,12 +58,7 @@ class Notepad:
58
58
  :return: next available notepad index
59
59
  """
60
60
  tabs = self.window.core.tabs.get_tabs_by_type(Tab.TAB_NOTEPAD)
61
- opened = False
62
- for tab in tabs:
63
- if tab.data_id == idx:
64
- opened = True
65
- break
66
- return opened
61
+ return any(tab.data_id == idx for tab in tabs)
67
62
 
68
63
  def create(
69
64
  self,
@@ -77,27 +72,30 @@ class Notepad:
77
72
  :param tab: existing tab to use (optional)
78
73
  :return: notepad widget (TabBody)
79
74
  """
80
- if idx is None:
81
- idx = self.window.core.tabs.count_by_type(Tab.TAB_NOTEPAD) + 1
82
- suffix = self.get_next_suffix()
83
- else:
84
- suffix = self.get_next_suffix()
75
+ tabs_core = self.window.core.tabs
76
+ existing_tabs = tabs_core.get_tabs_by_type(Tab.TAB_NOTEPAD)
77
+ used_ids = {t.data_id for t in existing_tabs}
85
78
 
86
- while self.is_opened(idx):
79
+ if idx is None:
80
+ idx = 1
81
+ while idx in used_ids:
87
82
  idx += 1
88
- suffix = self.get_next_suffix()
83
+
84
+ suffix = self.get_next_suffix()
89
85
 
90
86
  data_id = idx
91
- self.window.ui.notepad[data_id] = NotepadWidget(self.window)
92
- self.window.ui.notepad[data_id].id = idx
93
- self.window.ui.notepad[data_id].textarea.id = idx
94
- title = trans('output.tab.notepad')
95
- title += " " + str(suffix)
87
+ widget = NotepadWidget(self.window)
88
+ widget.id = idx
89
+ widget.textarea.id = idx
90
+ self.window.ui.notepad[data_id] = widget
91
+
92
+ title = trans('output.tab.notepad') + " " + str(suffix)
96
93
  if tab:
97
94
  if not tab.title:
98
95
  tab.title = title
99
- child = self.window.core.tabs.from_widget(self.window.ui.notepad[data_id])
100
- child.on_delete = self.on_delete # cleanup callback
96
+
97
+ child = tabs_core.from_widget(widget)
98
+ child.on_delete = self.on_delete
101
99
  return child, idx, data_id
102
100
 
103
101
  def on_delete(self, body: TabBody):
@@ -115,6 +113,8 @@ class Notepad:
115
113
  if idx in self.window.ui.notepad:
116
114
  self.window.ui.notepad[idx].on_delete()
117
115
  del self.window.ui.notepad[idx]
116
+ if idx in self.opened_idx:
117
+ self.opened_idx.discard(idx)
118
118
  self.update()
119
119
 
120
120
  def load(self):
@@ -130,9 +130,10 @@ class Notepad:
130
130
  items[idx] = item
131
131
 
132
132
  if num_notepads > 0:
133
- for idx in items:
134
- if idx in self.window.ui.notepad:
135
- self.window.ui.notepad[idx].setText(items[idx].content)
133
+ for idx, item in items.items():
134
+ widget = self.window.ui.notepad.get(idx)
135
+ if widget is not None:
136
+ widget.setText(item.content)
136
137
 
137
138
  def get_notepad_name(self, idx: int):
138
139
  """
@@ -159,17 +160,19 @@ class Notepad:
159
160
 
160
161
  :param idx: notepad idx
161
162
  """
162
- item = self.window.core.notepad.get_by_id(idx)
163
+ core_notepad = self.window.core.notepad
164
+ item = core_notepad.get_by_id(idx)
163
165
  if item is None:
164
166
  item = NotepadItem()
165
167
  item.idx = idx
166
- self.window.core.notepad.items[idx] = item
167
-
168
- if idx in self.window.ui.notepad:
169
- prev_content = item.content
170
- item.content = self.window.ui.notepad[idx].toPlainText()
171
- if prev_content != item.content: # update only if content changed
172
- self.window.core.notepad.update(item)
168
+ core_notepad.items[idx] = item
169
+
170
+ widget = self.window.ui.notepad.get(idx)
171
+ if widget is not None:
172
+ text = widget.toPlainText()
173
+ if item.content != text:
174
+ item.content = text
175
+ core_notepad.update(item)
173
176
  self.update()
174
177
 
175
178
  def save_all(self):
@@ -177,16 +180,18 @@ class Notepad:
177
180
  items = self.window.core.notepad.get_all()
178
181
  num_notepads = self.get_num_notepads()
179
182
  if num_notepads > 0:
180
- tabs = self.window.core.tabs.get_tabs_by_type(Tab.TAB_NOTEPAD)
181
- for tab in tabs:
183
+ ui_notepad = self.window.ui.notepad
184
+ for tab in self.window.core.tabs.get_tabs_by_type(Tab.TAB_NOTEPAD):
182
185
  idx = tab.data_id
183
- if idx in self.window.ui.notepad:
184
- if idx in items:
185
- prev_content = str(items[idx].content)
186
- items[idx].content = self.window.ui.notepad[idx].toPlainText()
187
- # update only if content changed
188
- if prev_content != items[idx].content:
189
- self.window.core.notepad.update(items[idx])
186
+ widget = ui_notepad.get(idx)
187
+ if widget is None:
188
+ continue
189
+ if idx in items:
190
+ prev_content = str(items[idx].content)
191
+ text = widget.toPlainText()
192
+ if prev_content != text:
193
+ items[idx].content = text
194
+ self.window.core.notepad.update(items[idx])
190
195
  self.update()
191
196
 
192
197
  def setup(self):
@@ -200,21 +205,19 @@ class Notepad:
200
205
  :param text: text to append
201
206
  :param idx: notepad idx
202
207
  """
203
- if idx not in self.window.ui.notepad:
208
+ widget = self.window.ui.notepad.get(idx)
209
+ if widget is None:
204
210
  return
205
- dt = "" # TODO: add to config append date/time
206
- # dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ":\n--------------------------\n"
207
- prev_text = self.window.ui.notepad[idx].toPlainText()
208
- if prev_text.strip() != "":
209
- prev_text += "\n"
210
- new_text = prev_text + dt + text.strip()
211
- self.window.ui.notepad[idx].setText(new_text)
212
- self.save(idx)
213
-
214
- # move cursor to end
215
- cursor = self.window.ui.notepad[idx].textarea.textCursor()
211
+ dt = ""
212
+ prev_text = widget.toPlainText()
213
+ need_nl = prev_text.strip() != ""
214
+ textarea = widget.textarea
215
+ cursor = textarea.textCursor()
216
216
  cursor.movePosition(QTextCursor.End)
217
- self.window.ui.notepad[idx].textarea.setTextCursor(cursor)
217
+ to_insert = (("\n" if need_nl else "") + dt + text.strip())
218
+ cursor.insertText(to_insert)
219
+ textarea.setTextCursor(cursor)
220
+ self.save(idx)
218
221
 
219
222
  def get_num_notepads(self) -> int:
220
223
  """
@@ -230,8 +233,9 @@ class Notepad:
230
233
 
231
234
  :return: current notepad index
232
235
  """
236
+ tabs_ctrl = self.window.controller.ui.tabs
233
237
  if self.is_active():
234
- tab = self.window.controller.ui.tabs.get_current_tab()
238
+ tab = tabs_ctrl.get_current_tab()
235
239
  if tab is not None:
236
240
  return tab.data_id
237
241
  return 1
@@ -246,17 +250,14 @@ class Notepad:
246
250
 
247
251
  def open(self):
248
252
  """Open notepad"""
249
- # if notepad disabled, do nothing
250
253
  if self.get_num_notepads() == 0:
251
254
  return
252
-
253
- # switch to first notepad tab if current tab is not notepad
254
- tabs = self.window.ui.layout.get_active_tabs()
255
255
  if self.window.controller.ui.tabs.get_current_type() != Tab.TAB_NOTEPAD:
256
256
  idx = self.window.core.tabs.get_min_idx_by_type(Tab.TAB_NOTEPAD)
257
257
  if idx is not None:
258
+ tabs = self.window.ui.layout.get_active_tabs()
258
259
  tabs.setCurrentIndex(idx)
259
- self.window.activateWindow() # focus
260
+ self.window.activateWindow()
260
261
 
261
262
  def update(self):
262
263
  """Update notepads UI"""
@@ -299,9 +300,8 @@ class Notepad:
299
300
  :return: current notepad text
300
301
  """
301
302
  idx = self.get_current_active()
302
- if idx in self.window.ui.notepad:
303
- return self.window.ui.notepad[idx].toPlainText()
304
- return ""
303
+ widget = self.window.ui.notepad.get(idx)
304
+ return widget.toPlainText() if widget is not None else ""
305
305
 
306
306
  def get_notepad_text(self, idx: int) -> str:
307
307
  """
@@ -310,9 +310,8 @@ class Notepad:
310
310
  :param idx: notepad index
311
311
  :return: notepad text
312
312
  """
313
- if idx in self.window.ui.notepad:
314
- return self.window.ui.notepad[idx].toPlainText()
315
- return ""
313
+ widget = self.window.ui.notepad.get(idx)
314
+ return widget.toPlainText() if widget is not None else ""
316
315
 
317
316
  def clear(self, idx: int) -> bool:
318
317
  """
@@ -320,8 +319,9 @@ class Notepad:
320
319
 
321
320
  :param idx: notepad idx
322
321
  """
323
- if idx in self.window.ui.notepad:
324
- self.window.ui.notepad[idx].textarea.clear()
322
+ widget = self.window.ui.notepad.get(idx)
323
+ if widget is not None:
324
+ widget.textarea.clear()
325
325
  self.save(idx)
326
326
  return True
327
327
  return False
@@ -342,10 +342,12 @@ class Notepad:
342
342
  return
343
343
  if tab.type == Tab.TAB_NOTEPAD:
344
344
  idx = tab.data_id
345
- if idx in self.window.ui.notepad:
346
- if idx not in self.opened_idx:
347
- QTimer.singleShot(0, self.window.ui.notepad[idx].scroll_to_bottom)
348
- if not self.window.ui.notepad[idx].opened:
349
- self.window.ui.notepad[idx].opened = True
350
- if idx not in self.opened_idx:
351
- self.opened_idx.append(idx)
345
+ widget = self.window.ui.notepad.get(idx)
346
+ if widget is None:
347
+ return
348
+ if idx not in self.opened_idx:
349
+ QTimer.singleShot(0, widget.scroll_to_bottom)
350
+ if not widget.opened:
351
+ widget.opened = True
352
+ if idx not in self.opened_idx:
353
+ self.opened_idx.add(idx)
@@ -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.15 03:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any
@@ -138,10 +138,9 @@ class Settings:
138
138
  window.controller.plugins.presets.save_current()
139
139
  window.core.config.save()
140
140
  self.close()
141
- window.update_status(trans('info.settings.saved'))
142
141
 
143
- event = Event(Event.PLUGIN_SETTINGS_CHANGED)
144
- window.dispatch(event)
142
+ window.update_status(trans('info.settings.saved'))
143
+ window.dispatch(Event(Event.PLUGIN_SETTINGS_CHANGED))
145
144
  window.controller.ui.update_tokens()
146
145
 
147
146
  def close(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.11 19:00:00 #
9
+ # Updated Date: 2025.08.24 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -67,7 +67,7 @@ class Editor:
67
67
  self.window.ui.add_hook("update.config.notepad.num", self.hook_update)
68
68
  self.window.ui.add_hook("update.config.render.code_syntax", self.hook_update)
69
69
  self.window.ui.add_hook("update.config.theme.style", self.hook_update)
70
- self.window.ui.add_hook("update.config.llama.idx.chat.agent.render.all", self.hook_update)
70
+ self.window.ui.add_hook("update.config.agent.output.render.all", self.hook_update)
71
71
  self.window.ui.add_hook("update.config.audio.input.backend", self.hook_update)
72
72
  self.window.ui.add_hook("update.config.audio.output.backend", self.hook_update)
73
73
  # self.window.ui.add_hook("llama.idx.storage", self.hook_update) # vector store update
@@ -204,7 +204,7 @@ class Editor:
204
204
  if self.config_changed('ctx.sources') or self.config_changed('ctx.audio'):
205
205
  self.window.controller.ctx.refresh()
206
206
 
207
- if self.config_changed('llama.idx.chat.agent.render.all'):
207
+ if self.config_changed('agent.output.render.all'):
208
208
  self.window.controller.chat.render.reload()
209
209
 
210
210
  # update global shortcuts
@@ -294,7 +294,7 @@ class Editor:
294
294
  self.window.core.config.set(key, value)
295
295
  self.window.controller.ctx.update()
296
296
 
297
- elif key == "llama.idx.chat.agent.render.all":
297
+ elif key == "agent.output.render.all":
298
298
  self.window.core.config.set(key, value)
299
299
  self.window.controller.chat.render.reload()
300
300