pygpt-net 2.6.29__py3-none-any.whl → 2.6.31__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 (182) hide show
  1. pygpt_net/CHANGELOG.txt +15 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +4 -0
  4. pygpt_net/{container.py → app_core.py} +5 -6
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/access/control.py +1 -9
  7. pygpt_net/controller/assistant/assistant.py +4 -4
  8. pygpt_net/controller/assistant/batch.py +7 -7
  9. pygpt_net/controller/assistant/files.py +4 -4
  10. pygpt_net/controller/assistant/threads.py +3 -3
  11. pygpt_net/controller/attachment/attachment.py +4 -7
  12. pygpt_net/controller/audio/audio.py +25 -1
  13. pygpt_net/controller/audio/ui.py +2 -2
  14. pygpt_net/controller/chat/audio.py +1 -8
  15. pygpt_net/controller/chat/common.py +30 -4
  16. pygpt_net/controller/chat/handler/stream_worker.py +1124 -0
  17. pygpt_net/controller/chat/output.py +8 -3
  18. pygpt_net/controller/chat/stream.py +4 -405
  19. pygpt_net/controller/chat/text.py +3 -2
  20. pygpt_net/controller/chat/vision.py +11 -19
  21. pygpt_net/controller/config/placeholder.py +1 -1
  22. pygpt_net/controller/ctx/ctx.py +1 -1
  23. pygpt_net/controller/ctx/summarizer.py +1 -1
  24. pygpt_net/controller/kernel/kernel.py +11 -3
  25. pygpt_net/controller/kernel/reply.py +5 -1
  26. pygpt_net/controller/mode/mode.py +21 -12
  27. pygpt_net/controller/plugins/settings.py +3 -2
  28. pygpt_net/controller/presets/editor.py +112 -99
  29. pygpt_net/controller/realtime/__init__.py +12 -0
  30. pygpt_net/controller/realtime/manager.py +53 -0
  31. pygpt_net/controller/realtime/realtime.py +268 -0
  32. pygpt_net/controller/theme/theme.py +3 -2
  33. pygpt_net/controller/ui/mode.py +7 -0
  34. pygpt_net/controller/ui/ui.py +19 -1
  35. pygpt_net/controller/ui/vision.py +4 -4
  36. pygpt_net/core/agents/legacy.py +2 -2
  37. pygpt_net/core/agents/runners/openai_workflow.py +2 -2
  38. pygpt_net/core/assistants/files.py +5 -5
  39. pygpt_net/core/assistants/store.py +4 -4
  40. pygpt_net/core/audio/audio.py +6 -1
  41. pygpt_net/core/audio/backend/native/__init__.py +12 -0
  42. pygpt_net/core/audio/backend/{native.py → native/native.py} +426 -127
  43. pygpt_net/core/audio/backend/native/player.py +139 -0
  44. pygpt_net/core/audio/backend/native/realtime.py +250 -0
  45. pygpt_net/core/audio/backend/pyaudio/__init__.py +12 -0
  46. pygpt_net/core/audio/backend/pyaudio/playback.py +194 -0
  47. pygpt_net/core/audio/backend/pyaudio/pyaudio.py +923 -0
  48. pygpt_net/core/audio/backend/pyaudio/realtime.py +275 -0
  49. pygpt_net/core/audio/backend/pygame/__init__.py +12 -0
  50. pygpt_net/core/audio/backend/{pygame.py → pygame/pygame.py} +130 -19
  51. pygpt_net/core/audio/backend/shared/__init__.py +38 -0
  52. pygpt_net/core/audio/backend/shared/conversions.py +211 -0
  53. pygpt_net/core/audio/backend/shared/envelope.py +38 -0
  54. pygpt_net/core/audio/backend/shared/player.py +137 -0
  55. pygpt_net/core/audio/backend/shared/rt.py +52 -0
  56. pygpt_net/core/audio/capture.py +5 -0
  57. pygpt_net/core/audio/output.py +13 -2
  58. pygpt_net/core/audio/whisper.py +6 -2
  59. pygpt_net/core/bridge/bridge.py +4 -3
  60. pygpt_net/core/bridge/worker.py +31 -9
  61. pygpt_net/core/debug/console/console.py +2 -2
  62. pygpt_net/core/debug/presets.py +2 -2
  63. pygpt_net/core/dispatcher/dispatcher.py +37 -1
  64. pygpt_net/core/events/__init__.py +2 -1
  65. pygpt_net/core/events/realtime.py +55 -0
  66. pygpt_net/core/experts/experts.py +2 -2
  67. pygpt_net/core/image/image.py +51 -1
  68. pygpt_net/core/modes/modes.py +2 -2
  69. pygpt_net/core/presets/presets.py +3 -3
  70. pygpt_net/core/realtime/options.py +87 -0
  71. pygpt_net/core/realtime/shared/__init__.py +0 -0
  72. pygpt_net/core/realtime/shared/audio.py +213 -0
  73. pygpt_net/core/realtime/shared/loop.py +64 -0
  74. pygpt_net/core/realtime/shared/session.py +59 -0
  75. pygpt_net/core/realtime/shared/text.py +37 -0
  76. pygpt_net/core/realtime/shared/tools.py +276 -0
  77. pygpt_net/core/realtime/shared/turn.py +38 -0
  78. pygpt_net/core/realtime/shared/types.py +16 -0
  79. pygpt_net/core/realtime/worker.py +164 -0
  80. pygpt_net/core/tokens/tokens.py +4 -4
  81. pygpt_net/core/types/__init__.py +1 -0
  82. pygpt_net/core/types/image.py +48 -0
  83. pygpt_net/core/types/mode.py +5 -2
  84. pygpt_net/core/vision/analyzer.py +1 -1
  85. pygpt_net/data/config/config.json +13 -4
  86. pygpt_net/data/config/models.json +219 -101
  87. pygpt_net/data/config/modes.json +3 -9
  88. pygpt_net/data/config/settings.json +135 -27
  89. pygpt_net/data/config/settings_section.json +2 -2
  90. pygpt_net/data/locale/locale.de.ini +7 -7
  91. pygpt_net/data/locale/locale.en.ini +25 -12
  92. pygpt_net/data/locale/locale.es.ini +7 -7
  93. pygpt_net/data/locale/locale.fr.ini +7 -7
  94. pygpt_net/data/locale/locale.it.ini +7 -7
  95. pygpt_net/data/locale/locale.pl.ini +8 -8
  96. pygpt_net/data/locale/locale.uk.ini +7 -7
  97. pygpt_net/data/locale/locale.zh.ini +3 -3
  98. pygpt_net/data/locale/plugin.audio_input.en.ini +4 -0
  99. pygpt_net/data/locale/plugin.audio_output.en.ini +4 -0
  100. pygpt_net/item/model.py +23 -3
  101. pygpt_net/plugin/audio_input/plugin.py +37 -4
  102. pygpt_net/plugin/audio_input/simple.py +57 -8
  103. pygpt_net/plugin/cmd_files/worker.py +3 -0
  104. pygpt_net/plugin/openai_dalle/plugin.py +4 -4
  105. pygpt_net/plugin/openai_vision/plugin.py +12 -13
  106. pygpt_net/provider/agents/openai/agent.py +5 -5
  107. pygpt_net/provider/agents/openai/agent_b2b.py +5 -5
  108. pygpt_net/provider/agents/openai/agent_planner.py +5 -6
  109. pygpt_net/provider/agents/openai/agent_with_experts.py +5 -5
  110. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -4
  111. pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -4
  112. pygpt_net/provider/agents/openai/bot_researcher.py +2 -2
  113. pygpt_net/provider/agents/openai/bots/research_bot/agents/planner_agent.py +1 -1
  114. pygpt_net/provider/agents/openai/bots/research_bot/agents/search_agent.py +1 -1
  115. pygpt_net/provider/agents/openai/bots/research_bot/agents/writer_agent.py +1 -1
  116. pygpt_net/provider/agents/openai/evolve.py +5 -5
  117. pygpt_net/provider/agents/openai/supervisor.py +4 -4
  118. pygpt_net/provider/api/__init__.py +27 -0
  119. pygpt_net/provider/api/anthropic/__init__.py +68 -0
  120. pygpt_net/provider/api/google/__init__.py +295 -0
  121. pygpt_net/provider/api/google/audio.py +121 -0
  122. pygpt_net/provider/api/google/chat.py +591 -0
  123. pygpt_net/provider/api/google/image.py +427 -0
  124. pygpt_net/provider/api/google/realtime/__init__.py +12 -0
  125. pygpt_net/provider/api/google/realtime/client.py +1945 -0
  126. pygpt_net/provider/api/google/realtime/realtime.py +186 -0
  127. pygpt_net/provider/api/google/tools.py +222 -0
  128. pygpt_net/provider/api/google/vision.py +129 -0
  129. pygpt_net/provider/{gpt → api/openai}/__init__.py +24 -4
  130. pygpt_net/provider/api/openai/agents/__init__.py +0 -0
  131. pygpt_net/provider/{gpt → api/openai}/agents/computer.py +1 -1
  132. pygpt_net/provider/{gpt → api/openai}/agents/experts.py +1 -1
  133. pygpt_net/provider/{gpt → api/openai}/agents/response.py +1 -1
  134. pygpt_net/provider/{gpt → api/openai}/assistants.py +1 -1
  135. pygpt_net/provider/{gpt → api/openai}/chat.py +15 -8
  136. pygpt_net/provider/{gpt → api/openai}/completion.py +1 -1
  137. pygpt_net/provider/{gpt → api/openai}/image.py +1 -1
  138. pygpt_net/provider/api/openai/realtime/__init__.py +12 -0
  139. pygpt_net/provider/api/openai/realtime/client.py +1828 -0
  140. pygpt_net/provider/api/openai/realtime/realtime.py +194 -0
  141. pygpt_net/provider/{gpt → api/openai}/remote_tools.py +1 -1
  142. pygpt_net/provider/{gpt → api/openai}/responses.py +34 -20
  143. pygpt_net/provider/{gpt → api/openai}/store.py +2 -2
  144. pygpt_net/provider/{gpt → api/openai}/vision.py +1 -1
  145. pygpt_net/provider/api/openai/worker/__init__.py +0 -0
  146. pygpt_net/provider/{gpt → api/openai}/worker/assistants.py +4 -4
  147. pygpt_net/provider/{gpt → api/openai}/worker/importer.py +10 -10
  148. pygpt_net/provider/audio_input/google_genai.py +103 -0
  149. pygpt_net/provider/audio_input/openai_whisper.py +1 -1
  150. pygpt_net/provider/audio_output/google_genai_tts.py +229 -0
  151. pygpt_net/provider/audio_output/openai_tts.py +9 -6
  152. pygpt_net/provider/core/config/patch.py +26 -0
  153. pygpt_net/provider/core/model/patch.py +20 -0
  154. pygpt_net/provider/core/preset/json_file.py +2 -4
  155. pygpt_net/provider/llms/anthropic.py +2 -5
  156. pygpt_net/provider/llms/base.py +4 -3
  157. pygpt_net/provider/llms/google.py +8 -9
  158. pygpt_net/provider/llms/openai.py +1 -1
  159. pygpt_net/provider/loaders/hub/image_vision/base.py +1 -1
  160. pygpt_net/ui/dialog/preset.py +71 -55
  161. pygpt_net/ui/layout/toolbox/footer.py +16 -0
  162. pygpt_net/ui/layout/toolbox/image.py +5 -0
  163. pygpt_net/ui/main.py +6 -4
  164. pygpt_net/ui/widget/option/combo.py +15 -1
  165. pygpt_net/utils.py +9 -0
  166. {pygpt_net-2.6.29.dist-info → pygpt_net-2.6.31.dist-info}/METADATA +55 -55
  167. {pygpt_net-2.6.29.dist-info → pygpt_net-2.6.31.dist-info}/RECORD +181 -135
  168. pygpt_net/core/audio/backend/pyaudio.py +0 -554
  169. /pygpt_net/{provider/gpt/agents → controller/chat/handler}/__init__.py +0 -0
  170. /pygpt_net/{provider/gpt/worker → core/realtime}/__init__.py +0 -0
  171. /pygpt_net/provider/{gpt → api/openai}/agents/client.py +0 -0
  172. /pygpt_net/provider/{gpt → api/openai}/agents/remote_tools.py +0 -0
  173. /pygpt_net/provider/{gpt → api/openai}/agents/utils.py +0 -0
  174. /pygpt_net/provider/{gpt → api/openai}/audio.py +0 -0
  175. /pygpt_net/provider/{gpt → api/openai}/computer.py +0 -0
  176. /pygpt_net/provider/{gpt → api/openai}/container.py +0 -0
  177. /pygpt_net/provider/{gpt → api/openai}/summarizer.py +0 -0
  178. /pygpt_net/provider/{gpt → api/openai}/tools.py +0 -0
  179. /pygpt_net/provider/{gpt → api/openai}/utils.py +0 -0
  180. {pygpt_net-2.6.29.dist-info → pygpt_net-2.6.31.dist-info}/LICENSE +0 -0
  181. {pygpt_net-2.6.29.dist-info → pygpt_net-2.6.31.dist-info}/WHEEL +0 -0
  182. {pygpt_net-2.6.29.dist-info → pygpt_net-2.6.31.dist-info}/entry_points.txt +0 -0
@@ -6,12 +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.14 13:00:00 #
9
+ # Updated Date: 2025.08.28 09:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
13
13
  from PySide6.QtWidgets import QPushButton, QHBoxLayout, QLabel, QVBoxLayout, QSplitter, QWidget, QSizePolicy, \
14
- QTabWidget, QLineEdit, QFileDialog
14
+ QTabWidget, QFileDialog
15
15
 
16
16
  from pygpt_net.core.types import (
17
17
  MODE_AGENT,
@@ -51,12 +51,8 @@ class Preset(BaseConfigDialog):
51
51
  """Setup preset editor dialog"""
52
52
  self.window.ui.nodes['preset.btn.current'] = QPushButton(trans("dialog.preset.btn.current"))
53
53
  self.window.ui.nodes['preset.btn.save'] = QPushButton(trans("dialog.preset.btn.save"))
54
- self.window.ui.nodes['preset.btn.current'].clicked.connect(
55
- lambda: self.window.controller.presets.editor.from_current()
56
- )
57
- self.window.ui.nodes['preset.btn.save'].clicked.connect(
58
- lambda: self.window.controller.presets.editor.save()
59
- )
54
+ self.window.ui.nodes['preset.btn.current'].clicked.connect(self.window.controller.presets.editor.from_current)
55
+ self.window.ui.nodes['preset.btn.save'].clicked.connect(self.window.controller.presets.editor.save)
60
56
 
61
57
  self.window.ui.nodes['preset.btn.current'].setAutoDefault(False)
62
58
  self.window.ui.nodes['preset.btn.save'].setAutoDefault(True)
@@ -119,14 +115,14 @@ class Preset(BaseConfigDialog):
119
115
  mode_keys_middle = [
120
116
  MODE_COMPLETION,
121
117
  MODE_IMAGE,
122
- MODE_VISION,
118
+ # MODE_VISION,
123
119
  MODE_COMPUTER,
120
+ MODE_EXPERT,
124
121
  ]
125
122
  mode_keys_right = [
126
123
  MODE_AGENT_LLAMA,
127
124
  MODE_AGENT_OPENAI,
128
125
  MODE_AGENT,
129
- MODE_EXPERT,
130
126
  ]
131
127
 
132
128
  rows_mode_left = QVBoxLayout()
@@ -156,17 +152,20 @@ class Preset(BaseConfigDialog):
156
152
  rows_mode.addStretch(1)
157
153
 
158
154
  # modes
159
- self.window.ui.nodes['preset.editor.modes'] = QWidget()
160
- self.window.ui.nodes['preset.editor.modes'].setLayout(rows_mode)
161
- self.window.ui.nodes['preset.editor.modes'].setContentsMargins(0, 0, 0, 0)
155
+ modes = QWidget()
156
+ modes.setLayout(rows_mode)
157
+ modes.setContentsMargins(0, 0, 0, 0)
158
+ self.window.ui.nodes['preset.editor.modes'] = modes
162
159
 
163
160
  # experts
164
161
  self.window.ui.nodes['preset.editor.experts'] = ExpertsEditor(self.window)
165
162
 
166
163
  # desc and prompt
167
- self.window.ui.nodes['preset.editor.description'] = QWidget()
168
- self.window.ui.nodes['preset.editor.description'].setLayout(options['description'])
169
- self.window.ui.nodes['preset.editor.description'].setContentsMargins(0, 5, 0, 5)
164
+ desc = QWidget()
165
+ desc.setLayout(options['description'])
166
+ desc.setContentsMargins(0, 5, 0, 5)
167
+ self.window.ui.nodes['preset.editor.description'] = desc
168
+
170
169
 
171
170
  # prompt + extra options
172
171
  prompt_layout = QVBoxLayout()
@@ -195,6 +194,7 @@ class Preset(BaseConfigDialog):
195
194
  "agent_provider_openai",
196
195
  "idx",
197
196
  ]
197
+ # personalize tab
198
198
  personalize_keys = [
199
199
  "ai_name",
200
200
  "user_name",
@@ -202,15 +202,18 @@ class Preset(BaseConfigDialog):
202
202
  "ai_personalize",
203
203
  ]
204
204
  for key in left_keys:
205
- self.window.ui.nodes['preset.editor.' + key] = QWidget()
206
- self.window.ui.nodes['preset.editor.' + key].setLayout(options[key])
207
- self.window.ui.nodes['preset.editor.' + key].setContentsMargins(0, 0, 0, 0)
208
- rows.addWidget(self.window.ui.nodes['preset.editor.' + key])
205
+ node_key = f"preset.editor.{key}"
206
+ node = QWidget()
207
+ node.setLayout(options[key])
208
+ node.setContentsMargins(0, 0, 0, 0)
209
+ rows.addWidget(node)
210
+ self.window.ui.nodes[node_key] = node
209
211
 
210
212
  # remote tools
211
- self.window.ui.nodes['preset.editor.remote_tools'] = QWidget()
212
- self.window.ui.nodes['preset.editor.remote_tools'].setLayout(options['remote_tools'])
213
- self.window.ui.nodes['preset.editor.remote_tools'].setContentsMargins(0, 0, 0, 0)
213
+ remote_tools = QWidget()
214
+ remote_tools.setLayout(options['remote_tools'])
215
+ remote_tools.setContentsMargins(0, 0, 0, 0)
216
+ self.window.ui.nodes['preset.editor.remote_tools'] = remote_tools
214
217
 
215
218
  rows_remote_tools = QVBoxLayout()
216
219
  rows_remote_tools.addWidget(self.window.ui.nodes['preset.editor.remote_tools'])
@@ -222,10 +225,12 @@ class Preset(BaseConfigDialog):
222
225
  # personalize
223
226
  personalize_rows = QVBoxLayout()
224
227
  for key in personalize_keys:
225
- self.window.ui.nodes['preset.editor.' + key] = QWidget()
226
- self.window.ui.nodes['preset.editor.' + key].setLayout(options[key])
227
- self.window.ui.nodes['preset.editor.' + key].setContentsMargins(0, 0, 0, 0)
228
- personalize_rows.addWidget(self.window.ui.nodes['preset.editor.' + key])
228
+ node_key = f"preset.editor.{key}"
229
+ node = QWidget()
230
+ node.setLayout(options[key])
231
+ node.setContentsMargins(0, 0, 0, 0)
232
+ personalize_rows.addWidget(node)
233
+ self.window.ui.nodes[node_key] = node
229
234
 
230
235
  self.window.ui.nodes['preset.editor.ai_avatar'].setVisible(False)
231
236
 
@@ -255,11 +260,12 @@ class Preset(BaseConfigDialog):
255
260
  widget_main = QWidget()
256
261
  widget_main.setLayout(main)
257
262
 
258
- self.window.ui.splitters['editor.presets'] = QSplitter(Qt.Vertical)
259
- self.window.ui.splitters['editor.presets'].addWidget(widget_main)
260
- self.window.ui.splitters['editor.presets'].addWidget(widget_prompt)
261
- self.window.ui.splitters['editor.presets'].setStretchFactor(0, 1)
262
- self.window.ui.splitters['editor.presets'].setStretchFactor(1, 2)
263
+ splitter = QSplitter(Qt.Vertical)
264
+ splitter.addWidget(widget_main)
265
+ splitter.addWidget(widget_prompt)
266
+ splitter.setStretchFactor(0, 1)
267
+ splitter.setStretchFactor(1, 2)
268
+ self.window.ui.splitters['editor.presets'] = splitter
263
269
 
264
270
  widget_personalize = QWidget()
265
271
  widget_personalize.setLayout(personalize_rows)
@@ -270,24 +276,26 @@ class Preset(BaseConfigDialog):
270
276
  widget_experts = QWidget()
271
277
  widget_experts.setLayout(experts_rows)
272
278
 
273
- self.window.ui.tabs['preset.editor.tabs'] = QTabWidget()
274
- self.window.ui.tabs['preset.editor.tabs'].addTab(self.window.ui.splitters['editor.presets'], trans("preset.tab.general"))
275
- self.window.ui.tabs['preset.editor.tabs'].addTab(widget_personalize, trans("preset.tab.personalize"))
276
- self.window.ui.tabs['preset.editor.tabs'].addTab(widget_experts, trans("preset.tab.experts"))
277
- self.window.ui.tabs['preset.editor.tabs'].addTab(widget_remote_tools, trans("preset.tab.remote_tools"))
279
+ tabs = QTabWidget()
280
+ tabs.addTab(splitter, trans("preset.tab.general"))
281
+ tabs.addTab(widget_personalize, trans("preset.tab.personalize"))
282
+ tabs.addTab(widget_experts, trans("preset.tab.experts"))
283
+ tabs.addTab(widget_remote_tools, trans("preset.tab.remote_tools"))
284
+ self.window.ui.tabs['preset.editor.tabs'] = tabs
278
285
 
279
286
  layout = QVBoxLayout()
280
287
  layout.addWidget(self.window.ui.tabs['preset.editor.tabs'])
281
288
  layout.addLayout(footer)
282
289
 
283
- self.window.ui.dialog['editor.' + self.dialog_id] = EditorDialog(self.window, self.dialog_id)
284
- self.window.ui.dialog['editor.' + self.dialog_id].setSizeGripEnabled(True)
285
- self.window.ui.dialog['editor.' + self.dialog_id].setWindowFlags(
286
- self.window.ui.dialog['editor.' + self.dialog_id].windowFlags() | Qt.WindowMaximizeButtonHint
290
+ dialog = EditorDialog(self.window, self.dialog_id)
291
+ dialog.setSizeGripEnabled(True)
292
+ dialog.setWindowFlags(
293
+ dialog.windowFlags() | Qt.WindowMaximizeButtonHint
287
294
  )
288
- self.window.ui.dialog['editor.' + self.dialog_id].setLayout(layout)
289
- self.window.ui.dialog['editor.' + self.dialog_id].setWindowTitle(trans('dialog.preset'))
290
- self.window.ui.dialog['editor.' + self.dialog_id].on_close_callback = self.on_close
295
+ dialog.setLayout(layout)
296
+ dialog.setWindowTitle(trans('dialog.preset'))
297
+ dialog.on_close_callback = self.on_close
298
+ self.window.ui.dialog['editor.' + self.dialog_id] = dialog
291
299
 
292
300
 
293
301
  def prepare_extra_config(self, prompt_layout):
@@ -301,15 +309,17 @@ class Preset(BaseConfigDialog):
301
309
  prompt_widget.setLayout(prompt_layout)
302
310
 
303
311
  self.window.ui.nodes['preset.editor.extra'] = {}
304
- self.window.ui.tabs['preset.editor.extra'] = QTabWidget()
305
- self.window.ui.tabs['preset.editor.extra'].addTab(
312
+
313
+ tabs = QTabWidget()
314
+ tabs.addTab(
306
315
  prompt_widget,
307
316
  trans("preset.prompt"),
308
317
  )
309
- self.window.ui.tabs['preset.editor.extra'].setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
310
- self.window.ui.tabs['preset.editor.extra'].setMinimumHeight(150)
318
+ tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
319
+ tabs.setMinimumHeight(150)
311
320
  layout = QVBoxLayout()
312
- layout.addWidget(self.window.ui.tabs['preset.editor.extra'])
321
+ layout.addWidget(tabs)
322
+ self.window.ui.tabs['preset.editor.extra'] = tabs
313
323
  return layout
314
324
 
315
325
 
@@ -368,6 +378,7 @@ class AvatarWidget(QWidget):
368
378
  self.init_ui()
369
379
 
370
380
  def init_ui(self):
381
+ """Initialize the avatar widget UI."""
371
382
  main_layout = QVBoxLayout(self)
372
383
  main_layout.setContentsMargins(0, 0, 0, 0)
373
384
 
@@ -397,13 +408,19 @@ class AvatarWidget(QWidget):
397
408
  main_layout.addStretch()
398
409
 
399
410
  def open_file_dialog(self):
411
+ """Open a file dialog to select an avatar image file."""
400
412
  file_name, _ = QFileDialog.getOpenFileName(
401
413
  self, trans("preset.personalize.avatar.choose.title"), "", "Images (*.png *.jpg *.jpeg *.bmp *.gif *.webp)"
402
414
  )
403
415
  if file_name:
404
416
  self.window.controller.presets.editor.upload_avatar(file_name)
405
417
 
406
- def load_avatar(self, file_path):
418
+ def load_avatar(self, file_path: str):
419
+ """
420
+ Load and display the avatar image from the given file path.
421
+
422
+ :param file_path: Path to the avatar image file
423
+ """
407
424
  from PySide6.QtGui import QPixmap
408
425
  pixmap = QPixmap(file_path)
409
426
  if not pixmap.isNull():
@@ -420,9 +437,7 @@ class AvatarWidget(QWidget):
420
437
  self.remove_button.setEnabled(enabled)
421
438
 
422
439
  def disable_remove_button(self):
423
- """
424
- Disable the remove button.
425
- """
440
+ """Disable the remove button."""
426
441
  self.enable_remove_button(False)
427
442
 
428
443
  def get_cover_pixmap(self, pixmap, target_width, target_height):
@@ -432,6 +447,7 @@ class AvatarWidget(QWidget):
432
447
  :param pixmap: Original pixmap
433
448
  :param target_width: Target width for the avatar preview
434
449
  :param target_height: Target height for the avatar preview
450
+ :return: Scaled and cropped pixmap
435
451
  """
436
452
  factor = max(target_width / pixmap.width(), target_height / pixmap.height())
437
453
  new_width = int(pixmap.width() * factor)
@@ -439,9 +455,9 @@ class AvatarWidget(QWidget):
439
455
  scaled_pix = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
440
456
  x = (scaled_pix.width() - target_width) // 2
441
457
  y = (scaled_pix.height() - target_height) // 2
442
- cropped_pix = scaled_pix.copy(x, y, target_width, target_height)
443
- return cropped_pix
458
+ return scaled_pix.copy(x, y, target_width, target_height)
444
459
 
445
460
  def remove_avatar(self):
461
+ """Remove the current avatar image."""
446
462
  self.avatar_preview.clear()
447
463
  self.remove_button.setEnabled(False)
@@ -81,6 +81,21 @@ class Footer:
81
81
  rows.addWidget(self.window.ui.nodes['voice.control.btn'])
82
82
  rows.setContentsMargins(2, 0, 0, 0)
83
83
 
84
+ self.window.ui.nodes['audio.auto_turn'] = ToggleLabel(trans('audio.auto_turn'), label_position="left",
85
+ icon=":/icons/voice.svg",
86
+ parent=self.window)
87
+ self.window.ui.nodes['audio.auto_turn'].box.toggled.connect(
88
+ self.window.controller.audio.toggle_auto_turn
89
+ )
90
+ auto_turn_widget = QWidget(widget)
91
+ auto_turn_layout = QHBoxLayout(auto_turn_widget)
92
+
93
+ auto_turn_layout.addWidget(QLabel("", auto_turn_widget))
94
+ auto_turn_layout.addStretch(1)
95
+ auto_turn_layout.addWidget(self.window.ui.nodes['audio.auto_turn'])
96
+ auto_turn_layout.setContentsMargins(5, 0, 15, 0)
97
+ rows.addWidget(auto_turn_widget)
98
+
84
99
  self.window.ui.nodes['layout.split'] = ToggleLabel(trans('layout.split'), label_position="left",
85
100
  icon=":/icons/split_screen.svg",
86
101
  parent=self.window)
@@ -90,6 +105,7 @@ class Footer:
90
105
  split_widget = QWidget(widget)
91
106
  split_layout = QHBoxLayout(split_widget)
92
107
 
108
+
93
109
  split_layout.addWidget(QLabel("", split_widget))
94
110
  split_layout.addStretch(1)
95
111
  split_layout.addWidget(self.window.ui.nodes['layout.split'])
@@ -11,6 +11,7 @@
11
11
 
12
12
  from PySide6.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget, QCheckBox
13
13
 
14
+ from pygpt_net.ui.widget.option.combo import OptionCombo
14
15
  from pygpt_net.ui.widget.option.slider import OptionSlider
15
16
  from pygpt_net.utils import trans
16
17
 
@@ -55,6 +56,9 @@ class Image:
55
56
  conf_global['img_raw'] = QCheckBox(trans("img.raw"), parent=container)
56
57
  conf_global['img_raw'].toggled.connect(self.window.controller.chat.common.img_toggle_raw)
57
58
 
59
+ option_resolutions = self.window.core.image.get_resolution_option()
60
+ conf_global['img_resolution'] = OptionCombo(self.window, 'global', 'img_resolution', option_resolutions)
61
+
58
62
  cols = QHBoxLayout()
59
63
  cols.addWidget(conf_global['img_raw'])
60
64
  cols.addWidget(conf_global['img_variants'])
@@ -62,6 +66,7 @@ class Image:
62
66
  rows = QVBoxLayout()
63
67
  rows.addWidget(ui.nodes['img_variants.label'])
64
68
  rows.addLayout(cols)
69
+ rows.addWidget(conf_global['img_resolution'])
65
70
  rows.setContentsMargins(2, 5, 5, 5)
66
71
 
67
72
  container.setLayout(rows)
pygpt_net/ui/main.py CHANGED
@@ -17,12 +17,13 @@ from PySide6.QtWidgets import QMainWindow, QApplication
17
17
  from qt_material import QtStyleTools
18
18
 
19
19
  from pygpt_net.core.events import BaseEvent, KernelEvent, ControlEvent
20
- from pygpt_net.container import Container
20
+ from pygpt_net.app_core import Core
21
21
  from pygpt_net.controller import Controller
22
22
  from pygpt_net.tools import Tools
23
23
  from pygpt_net.ui import UI
24
24
  from pygpt_net.ui.widget.textarea.web import ChatWebOutput
25
- from pygpt_net.utils import get_app_meta
25
+ from pygpt_net.utils import get_app_meta, freeze_updates
26
+
26
27
 
27
28
  class MainWindow(QMainWindow, QtStyleTools):
28
29
 
@@ -63,7 +64,7 @@ class MainWindow(QMainWindow, QtStyleTools):
63
64
  self.meta = get_app_meta()
64
65
 
65
66
  # setup service container
66
- self.core = Container(self)
67
+ self.core = Core(self)
67
68
  self.core.init()
68
69
  self.core.patch() # patch version if needed
69
70
  self.core.post_setup()
@@ -85,7 +86,8 @@ class MainWindow(QMainWindow, QtStyleTools):
85
86
 
86
87
  # setup UI
87
88
  self.ui = UI(self)
88
- self.ui.init()
89
+ with freeze_updates(self):
90
+ self.ui.init()
89
91
 
90
92
  # global shortcuts
91
93
  self.shortcuts = []
@@ -79,6 +79,7 @@ class OptionCombo(QWidget):
79
79
  self.combo = NoScrollCombo()
80
80
  self.combo.currentIndexChanged.connect(self.on_combo_change)
81
81
  self.current_id = None
82
+ self.locked = False
82
83
 
83
84
  # add items
84
85
  self.update()
@@ -114,6 +115,12 @@ class OptionCombo(QWidget):
114
115
  self.combo.addItem(value, key)
115
116
  else:
116
117
  self.combo.addItem(item, item)
118
+ elif type(self.keys) is dict:
119
+ for key, value in self.keys.items():
120
+ if key.startswith("separator::"):
121
+ self.combo.addSeparator(value)
122
+ else:
123
+ self.combo.addItem(value, key)
117
124
 
118
125
  def set_value(self, value):
119
126
  """
@@ -135,16 +142,21 @@ class OptionCombo(QWidget):
135
142
  """
136
143
  return self.current_id
137
144
 
138
- def set_keys(self, keys):
145
+ def set_keys(self, keys, lock: bool = False):
139
146
  """
140
147
  Set keys
141
148
 
142
149
  :param keys: keys
150
+ :param lock: lock current value if True
143
151
  """
152
+ if lock:
153
+ self.locked = True # lock on_change
144
154
  self.keys = keys
145
155
  self.option["keys"] = keys
146
156
  self.combo.clear()
147
157
  self.update()
158
+ if lock:
159
+ self.locked = False
148
160
 
149
161
  def on_combo_change(self, index):
150
162
  """
@@ -153,6 +165,8 @@ class OptionCombo(QWidget):
153
165
  :param index: combo index
154
166
  :return:
155
167
  """
168
+ if self.locked:
169
+ return
156
170
  self.current_id = self.combo.itemData(index)
157
171
  self.window.controller.config.combo.on_update(self.parent_id, self.id, self.option, self.current_id)
158
172
 
pygpt_net/utils.py CHANGED
@@ -13,6 +13,7 @@ import json
13
13
  import os
14
14
  import re
15
15
  from datetime import datetime
16
+ from contextlib import contextmanager
16
17
 
17
18
  from PySide6 import QtCore, QtGui
18
19
  from PySide6.QtWidgets import QApplication
@@ -61,6 +62,14 @@ def trans(key: str, reload: bool = False, domain: str = None) -> str:
61
62
  return locale.get(key, domain)
62
63
 
63
64
 
65
+ @contextmanager
66
+ def freeze_updates(widget):
67
+ widget.setUpdatesEnabled(False)
68
+ try:
69
+ yield
70
+ finally:
71
+ widget.setUpdatesEnabled(True)
72
+
64
73
  def get_init_value(key: str = "__version__") -> str:
65
74
  """
66
75
  Return config value from __init__.py