pygpt-net 2.6.31__py3-none-any.whl → 2.6.33__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 (61) hide show
  1. pygpt_net/CHANGELOG.txt +12 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +3 -1
  4. pygpt_net/app_core.py +3 -1
  5. pygpt_net/config.py +3 -1
  6. pygpt_net/controller/__init__.py +5 -1
  7. pygpt_net/controller/audio/audio.py +13 -0
  8. pygpt_net/controller/chat/attachment.py +2 -0
  9. pygpt_net/controller/chat/common.py +18 -83
  10. pygpt_net/controller/lang/custom.py +2 -2
  11. pygpt_net/controller/media/__init__.py +12 -0
  12. pygpt_net/controller/media/media.py +115 -0
  13. pygpt_net/controller/painter/common.py +10 -11
  14. pygpt_net/controller/painter/painter.py +4 -12
  15. pygpt_net/controller/realtime/realtime.py +27 -2
  16. pygpt_net/controller/ui/mode.py +16 -2
  17. pygpt_net/core/audio/backend/pyaudio/realtime.py +51 -14
  18. pygpt_net/core/audio/output.py +3 -2
  19. pygpt_net/core/camera/camera.py +369 -53
  20. pygpt_net/core/image/image.py +6 -5
  21. pygpt_net/core/realtime/worker.py +1 -5
  22. pygpt_net/core/render/web/body.py +24 -3
  23. pygpt_net/core/text/utils.py +54 -2
  24. pygpt_net/core/types/image.py +7 -1
  25. pygpt_net/core/video/__init__.py +12 -0
  26. pygpt_net/core/video/video.py +290 -0
  27. pygpt_net/data/config/config.json +240 -212
  28. pygpt_net/data/config/models.json +243 -172
  29. pygpt_net/data/config/settings.json +194 -6
  30. pygpt_net/data/css/web-blocks.css +6 -0
  31. pygpt_net/data/css/web-chatgpt.css +6 -0
  32. pygpt_net/data/css/web-chatgpt_wide.css +6 -0
  33. pygpt_net/data/locale/locale.de.ini +31 -2
  34. pygpt_net/data/locale/locale.en.ini +41 -7
  35. pygpt_net/data/locale/locale.es.ini +31 -2
  36. pygpt_net/data/locale/locale.fr.ini +31 -2
  37. pygpt_net/data/locale/locale.it.ini +31 -2
  38. pygpt_net/data/locale/locale.pl.ini +34 -2
  39. pygpt_net/data/locale/locale.uk.ini +31 -2
  40. pygpt_net/data/locale/locale.zh.ini +31 -2
  41. pygpt_net/data/locale/plugin.cmd_web.en.ini +8 -0
  42. pygpt_net/item/model.py +22 -1
  43. pygpt_net/provider/api/google/__init__.py +38 -2
  44. pygpt_net/provider/api/google/video.py +364 -0
  45. pygpt_net/provider/api/openai/realtime/realtime.py +1 -2
  46. pygpt_net/provider/core/config/patch.py +226 -178
  47. pygpt_net/provider/core/model/patch.py +17 -2
  48. pygpt_net/provider/web/duckduck_search.py +212 -0
  49. pygpt_net/ui/layout/toolbox/audio.py +55 -0
  50. pygpt_net/ui/layout/toolbox/footer.py +14 -58
  51. pygpt_net/ui/layout/toolbox/image.py +3 -14
  52. pygpt_net/ui/layout/toolbox/raw.py +52 -0
  53. pygpt_net/ui/layout/toolbox/split.py +48 -0
  54. pygpt_net/ui/layout/toolbox/toolbox.py +8 -8
  55. pygpt_net/ui/layout/toolbox/video.py +49 -0
  56. pygpt_net/ui/widget/draw/painter.py +452 -84
  57. {pygpt_net-2.6.31.dist-info → pygpt_net-2.6.33.dist-info}/METADATA +28 -11
  58. {pygpt_net-2.6.31.dist-info → pygpt_net-2.6.33.dist-info}/RECORD +61 -51
  59. {pygpt_net-2.6.31.dist-info → pygpt_net-2.6.33.dist-info}/LICENSE +0 -0
  60. {pygpt_net-2.6.31.dist-info → pygpt_net-2.6.33.dist-info}/WHEEL +0 -0
  61. {pygpt_net-2.6.31.dist-info → pygpt_net-2.6.33.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,15 @@
1
+ 2.6.33 (2025-09-02)
2
+
3
+ - Added a "crop" option, auto-resize canvas, and layer support to Painter.
4
+ - Improved video capture from the camera.
5
+
6
+ 2.6.32 (2025-09-02)
7
+
8
+ - Added video generation and support for Google Veo 3 models.
9
+ - Introduced new predefined models: veo-3.0-generate-preview and veo-3.0-fast-generate-preview.
10
+ - Integrated DuckDuckGo as a search provider in the WebSearch plugin.
11
+ - Added "Loop" mode to Realtime + audio mode for automatic turn handling and continuous conversation without manually enabling the microphone.
12
+
1
13
  2.6.31 (2025-09-01)
2
14
 
3
15
  - Chat with Audio mode renamed to Realtime + audio.
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.09.01 00:00:00 #
9
+ # Updated Date: 2025.09.02 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.6.31"
17
- __build__ = "2025-09-01"
16
+ __version__ = "2.6.33"
17
+ __build__ = "2025-09-02"
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.27 20:00:00 #
9
+ # Updated Date: 2025.09.02 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -182,6 +182,7 @@ from pygpt_net.provider.audio_output.eleven_labs import ElevenLabsTextToSpeech
182
182
  # web search engine providers
183
183
  from pygpt_net.provider.web.google_custom_search import GoogleCustomSearch
184
184
  from pygpt_net.provider.web.microsoft_bing import MicrosoftBingSearch
185
+ from pygpt_net.provider.web.duckduck_search import DuckDuckGoSearch
185
186
 
186
187
  # tools
187
188
  from pygpt_net.tools.indexer import IndexerTool
@@ -342,6 +343,7 @@ def run(**kwargs):
342
343
  # register web providers
343
344
  launcher.add_web(GoogleCustomSearch())
344
345
  launcher.add_web(MicrosoftBingSearch())
346
+ launcher.add_web(DuckDuckGoSearch())
345
347
 
346
348
  # register custom web providers
347
349
  providers = kwargs.get('web', None)
pygpt_net/app_core.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.28 09:00:00 #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.config import Config
@@ -43,6 +43,7 @@ from pygpt_net.core.tabs import Tabs
43
43
  from pygpt_net.core.text import Text
44
44
  from pygpt_net.core.tokens import Tokens
45
45
  from pygpt_net.core.updater import Updater
46
+ from pygpt_net.core.video import Video
46
47
  from pygpt_net.core.vision import Vision
47
48
  from pygpt_net.core.web import Web
48
49
 
@@ -92,6 +93,7 @@ class Core:
92
93
  self.text = Text(window)
93
94
  self.tokens = Tokens(window)
94
95
  self.updater = Updater(window)
96
+ self.video = Video(window)
95
97
  self.vision = Vision(window)
96
98
  self.web = Web(window)
97
99
 
pygpt_net/config.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.18 01:00:00 #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -66,6 +66,8 @@ class Config:
66
66
  "presets": "presets",
67
67
  "upload": "upload",
68
68
  "tmp": "tmp",
69
+ "video": "video",
70
+ "music": "music",
69
71
  }
70
72
  self._app_path = None
71
73
  self._version_cache = version if version else None
@@ -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.30 06:00:00 #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.controller.access import Access
@@ -29,6 +29,7 @@ from pygpt_net.controller.kernel import Kernel
29
29
  from pygpt_net.controller.lang import Lang
30
30
  from pygpt_net.controller.launcher import Launcher
31
31
  from pygpt_net.controller.layout import Layout
32
+ from pygpt_net.controller.media import Media
32
33
  from pygpt_net.controller.mode import Mode
33
34
  from pygpt_net.controller.model import Model
34
35
  from pygpt_net.controller.notepad import Notepad
@@ -71,6 +72,7 @@ class Controller:
71
72
  self.lang = Lang(window)
72
73
  self.launcher = Launcher(window)
73
74
  self.layout = Layout(window)
75
+ self.media = Media(window)
74
76
  self.mode = Mode(window)
75
77
  self.model = Model(window)
76
78
  self.notepad = Notepad(window)
@@ -111,6 +113,7 @@ class Controller:
111
113
  self.camera.setup_ui()
112
114
  self.access.setup()
113
115
  self.realtime.setup()
116
+ self.media.setup()
114
117
 
115
118
  def post_setup(self):
116
119
  """Post-setup, after plugins are loaded"""
@@ -169,6 +172,7 @@ class Controller:
169
172
  self.lang.reload()
170
173
  self.debug.reload()
171
174
  self.chat.reload()
175
+ self.media.reload()
172
176
  self.window.tools.on_reload()
173
177
  self.access.reload()
174
178
  self.tools.reload()
@@ -40,12 +40,19 @@ class Audio:
40
40
  def setup(self):
41
41
  """Setup controller"""
42
42
  self.update()
43
+
44
+ # continuous input (notepad)
43
45
  if self.window.core.config.get("audio.input.continuous", False):
44
46
  self.window.ui.plugin_addon['audio.input.btn'].continuous.setChecked(True)
45
47
 
48
+ # auto turn (VAD)
46
49
  if self.window.core.config.get("audio.input.auto_turn", False):
47
50
  self.window.ui.nodes['audio.auto_turn'].box.setChecked(True)
48
51
 
52
+ # loop recording
53
+ if self.window.core.config.get("audio.input.loop", False):
54
+ self.window.ui.nodes['audio.loop'].box.setChecked(True)
55
+
49
56
  def execute_input_stop(self):
50
57
  """Execute input stop (from UI)"""
51
58
  self.window.dispatch(Event(Event.AUDIO_INPUT_RECORD_TOGGLE, {
@@ -67,6 +74,12 @@ class Audio:
67
74
  self.window.core.config.set("audio.input.auto_turn", value)
68
75
  self.window.core.config.save()
69
76
 
77
+ def toggle_loop(self):
78
+ """Toggle loop recording setting"""
79
+ value = self.window.ui.nodes['audio.loop'].box.isChecked()
80
+ self.window.core.config.set("audio.input.loop", value)
81
+ self.window.core.config.save()
82
+
70
83
  def toggle_input(
71
84
  self,
72
85
  state: bool,
@@ -126,6 +126,8 @@ class Attachment(QObject):
126
126
  self.uploaded = False
127
127
  auto_index = self.window.core.config.get("attachments_auto_index", False)
128
128
  attachments = self.window.core.attachments.get_all(mode, only_files=True)
129
+ if self.mode != self.MODE_QUERY_CONTEXT:
130
+ auto_index = False # disable auto index for full context and summary modes
129
131
 
130
132
  if self.is_verbose() and len(attachments) > 0:
131
133
  print(f"\nUploading attachments...\nWork Mode: {mode}")
@@ -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.27 07:00:00 #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -100,37 +100,6 @@ class Common:
100
100
  event = RenderEvent(RenderEvent.ON_SWITCH)
101
101
  self.window.dispatch(event) # switch renderer if needed
102
102
 
103
- # edit icons
104
- """
105
- if self.window.core.config.has('ctx.edit_icons'):
106
- self.window.ui.nodes['output.edit'].setChecked(self.window.core.config.get('ctx.edit_icons'))
107
- data = {
108
- "initialized": self.initialized,
109
- }
110
- if self.window.core.config.get('ctx.edit_icons'):
111
- event = RenderEvent(RenderEvent.ON_EDIT_ENABLE, data)
112
- else:
113
- event = RenderEvent(RenderEvent.ON_EDIT_DISABLE, data)
114
- self.window.dispatch(event)
115
- """
116
-
117
- # images generation
118
- if self.window.core.config.get('img_raw'):
119
- self.window.ui.config['global']['img_raw'].setChecked(True)
120
- else:
121
- self.window.ui.config['global']['img_raw'].setChecked(False)
122
-
123
- # image resolution
124
- resolution = self.window.core.config.get('img_resolution', '1024x1024')
125
- self.window.controller.config.apply_value(
126
- parent_id="global",
127
- key="img_resolution",
128
- option=self.window.core.image.get_resolution_option(),
129
- value=resolution,
130
- )
131
- if not self.initialized:
132
- self.window.ui.add_hook("update.global.img_resolution", self.hook_update)
133
-
134
103
  # set focus to input
135
104
  self.window.ui.nodes['input'].setFocus()
136
105
  self.initialized = True
@@ -404,29 +373,6 @@ class Common:
404
373
  event = RenderEvent(RenderEvent.ON_TS_DISABLE, data)
405
374
  self.window.dispatch(event)
406
375
 
407
- def toggle_raw(self, value: bool):
408
- """
409
- Toggle raw (plain) output
410
-
411
- :param value: value of the checkbox
412
- """
413
- self.window.core.config.set('render.plain', value)
414
- self.window.core.config.save()
415
-
416
- # update checkbox in settings dialog
417
- self.window.controller.config.checkbox.apply(
418
- 'config',
419
- 'render.plain',
420
- {
421
- 'value': value
422
- },
423
- )
424
- event = RenderEvent(RenderEvent.ON_SWITCH)
425
- self.window.dispatch(event)
426
-
427
- # restore previous font size
428
- self.window.controller.ui.update_font_size()
429
-
430
376
  def toggle_edit_icons(self, value: bool):
431
377
  """
432
378
  Toggle edit icons
@@ -444,39 +390,28 @@ class Common:
444
390
  event = RenderEvent(RenderEvent.ON_EDIT_DISABLE, data)
445
391
  self.window.dispatch(event)
446
392
 
447
- def img_enable_raw(self):
448
- """Enable help for images"""
449
- self.window.core.config.set('img_raw', True)
450
- self.window.core.config.save()
451
-
452
- def img_disable_raw(self):
453
- """Disable help for images"""
454
- self.window.core.config.set('img_raw', False)
455
- self.window.core.config.save()
456
-
457
- def img_toggle_raw(self, state: bool):
393
+ def toggle_raw(self, value: bool):
458
394
  """
459
- Toggle help for images
395
+ Toggle raw (plain) output
460
396
 
461
- :param state: state of checkbox
397
+ :param value: value of the checkbox
462
398
  """
463
- if not state:
464
- self.img_disable_raw()
465
- else:
466
- self.img_enable_raw()
399
+ self.window.core.config.set('render.plain', value)
400
+ self.window.core.config.save()
467
401
 
468
- def hook_update(self, key: str, value: Any, caller, *args, **kwargs):
469
- """
470
- Hook for updating image resolution
402
+ # update checkbox in settings dialog
403
+ self.window.controller.config.checkbox.apply(
404
+ 'config',
405
+ 'render.plain',
406
+ {
407
+ 'value': value
408
+ },
409
+ )
410
+ event = RenderEvent(RenderEvent.ON_SWITCH)
411
+ self.window.dispatch(event)
471
412
 
472
- :param key: config key
473
- :param value: new value
474
- :param caller: caller object
475
- """
476
- if key == "img_resolution":
477
- if not value:
478
- return
479
- self.window.core.config.set('img_resolution', value)
413
+ # restore previous font size
414
+ self.window.controller.ui.update_font_size()
480
415
 
481
416
  def save_text(
482
417
  self,
@@ -55,8 +55,8 @@ class Custom:
55
55
  self.window.ui.config['preset'][MODE_CHAT].box.setText(trans("preset.chat"))
56
56
  self.window.ui.config['preset'][MODE_COMPLETION].box.setText(trans("preset.completion"))
57
57
  self.window.ui.config['preset'][MODE_IMAGE].box.setText(trans("preset.img"))
58
- self.window.ui.config['preset'][MODE_VISION].box.setText(trans("preset.vision"))
59
- #self.window.ui.config['preset'][MODE_LANGCHAIN].box.setText(trans("preset.langchain"))
58
+ # self.window.ui.config['preset'][MODE_VISION].box.setText(trans("preset.vision"))
59
+ # self.window.ui.config['preset'][MODE_LANGCHAIN].box.setText(trans("preset.langchain"))
60
60
  self.window.ui.config['preset'][MODE_LLAMA_INDEX].box.setText(trans("preset.llama_index"))
61
61
  self.window.ui.config['preset'][MODE_AGENT].box.setText(trans("preset.agent"))
62
62
  self.window.ui.config['preset'][MODE_AGENT_LLAMA].box.setText(trans("preset.agent_llama"))
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
+ # ================================================== #
11
+
12
+ from .media import Media
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
+ # ================================================== #
11
+
12
+ from typing import Any
13
+
14
+
15
+ class Media:
16
+ def __init__(self, window=None):
17
+ """
18
+ Media (video, image, music) controller
19
+
20
+ :param window: Window instance
21
+ """
22
+ self.window = window
23
+ self.initialized = False
24
+
25
+ def setup(self):
26
+ """Setup UI"""
27
+ # raw mode for images/video
28
+ if self.window.core.config.get('img_raw'):
29
+ self.window.ui.config['global']['img_raw'].setChecked(True)
30
+ else:
31
+ self.window.ui.config['global']['img_raw'].setChecked(False)
32
+
33
+ # image: resolution
34
+ resolution = self.window.core.config.get('img_resolution', '1024x1024')
35
+ self.window.controller.config.apply_value(
36
+ parent_id="global",
37
+ key="img_resolution",
38
+ option=self.window.core.image.get_resolution_option(),
39
+ value=resolution,
40
+ )
41
+
42
+ # video: aspect ratio
43
+ aspect_ratio = self.window.core.config.get('video.aspect_ratio', '16:9')
44
+ self.window.controller.config.apply_value(
45
+ parent_id="global",
46
+ key="video.aspect_ratio",
47
+ option=self.window.core.video.get_aspect_ratio_option(),
48
+ value=aspect_ratio,
49
+ )
50
+
51
+ # -- add hooks --
52
+ if not self.initialized:
53
+ self.window.ui.add_hook("update.global.img_resolution", self.hook_update)
54
+ self.window.ui.add_hook("update.global.video.aspect_ratio", self.hook_update)
55
+
56
+ def reload(self):
57
+ """Reload UI"""
58
+ self.setup()
59
+
60
+ def hook_update(self, key: str, value: Any, caller, *args, **kwargs):
61
+ """
62
+ Hook for updating media options
63
+
64
+ :param key: config key
65
+ :param value: new value
66
+ :param caller: caller object
67
+ """
68
+ if key == "img_resolution":
69
+ if not value:
70
+ return
71
+ self.window.core.config.set('img_resolution', value)
72
+ elif key == "video.aspect_ratio":
73
+ if not value:
74
+ return
75
+ self.window.core.config.set('video.aspect_ratio', value)
76
+
77
+ def enable_raw(self):
78
+ """Enable prompt enhancement for images"""
79
+ self.window.core.config.set('img_raw', True)
80
+ self.window.core.config.save()
81
+
82
+ def disable_raw(self):
83
+ """Disable prompt enhancement for images"""
84
+ self.window.core.config.set('img_raw', False)
85
+ self.window.core.config.save()
86
+
87
+ def toggle_raw(self):
88
+ """Save prompt enhancement option for images"""
89
+ state = self.window.ui.config['global']['img_raw'].isChecked()
90
+ if not state:
91
+ self.disable_raw()
92
+ else:
93
+ self.enable_raw()
94
+
95
+ def is_image_model(self) -> bool:
96
+ """
97
+ Check if the model is an image generation model
98
+
99
+ :return: True if the model is an image generation model
100
+ """
101
+ current = self.window.core.config.get("model")
102
+ model_data = self.window.core.models.get(current)
103
+ if model_data:
104
+ return model_data.is_image_output()
105
+
106
+ def is_video_model(self) -> bool:
107
+ """
108
+ Check if the model is a video generation model
109
+
110
+ :return: True if the model is a video generation model
111
+ """
112
+ current = self.window.core.config.get("model")
113
+ model_data = self.window.core.models.get(current)
114
+ if model_data:
115
+ return model_data.is_video_output()
@@ -1,13 +1,4 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # ================================================== #
4
- # This file is a part of PYGPT package #
5
- # Website: https://pygpt.net #
6
- # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
- # MIT License #
8
- # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 08:00:00 #
10
- # ================================================== #
1
+ # controller/painter/common.py
11
2
 
12
3
  from typing import Tuple, Optional, Dict, List
13
4
 
@@ -49,8 +40,11 @@ class Common:
49
40
  :param enabled: bool
50
41
  """
51
42
  if enabled:
43
+ # keep UI color for compatibility
52
44
  self.window.ui.nodes['painter.select.brush.color'].setCurrentText("Black")
53
45
  self.window.ui.painter.set_brush_color(Qt.black)
46
+ # switch widget to brush mode (layered painting)
47
+ self.window.ui.painter.set_mode("brush")
54
48
  self.window.core.config.set('painter.brush.mode', "brush")
55
49
  self.window.core.config.save()
56
50
 
@@ -61,8 +55,11 @@ class Common:
61
55
  :param enabled: bool
62
56
  """
63
57
  if enabled:
58
+ # keep UI color for compatibility
64
59
  self.window.ui.nodes['painter.select.brush.color'].setCurrentText("White")
65
60
  self.window.ui.painter.set_brush_color(Qt.white)
61
+ # switch widget to erase mode (layered erasing)
62
+ self.window.ui.painter.set_mode("erase")
66
63
  self.window.core.config.set('painter.brush.mode', "erase")
67
64
  self.window.core.config.save()
68
65
 
@@ -76,8 +73,10 @@ class Common:
76
73
  selected = self.window.ui.nodes['painter.select.canvas.size'].currentData()
77
74
  if selected:
78
75
  size = self.convert_to_size(selected)
76
+ # setCurrentText might not exist in the combo's items for custom sizes; harmless if it doesn't match
79
77
  self.window.ui.nodes['painter.select.canvas.size'].setCurrentText(selected)
80
78
  self.set_canvas_size(size[0], size[1])
79
+ # resizing the widget triggers automatic image rescale in PainterWidget.resizeEvent
81
80
  self.window.core.config.set('painter.canvas.size', selected)
82
81
  self.window.core.config.save()
83
82
 
@@ -192,4 +191,4 @@ class Common:
192
191
 
193
192
  :return: path to capture directory
194
193
  """
195
- return self.window.core.config.get_user_dir('capture')
194
+ return self.window.core.config.get_user_dir('capture')
@@ -1,13 +1,4 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # ================================================== #
4
- # This file is a part of PYGPT package #
5
- # Website: https://pygpt.net #
6
- # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
- # MIT License #
8
- # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.20 21:00:00 #
10
- # ================================================== #
1
+ # controller/painter/painter.py
11
2
 
12
3
  import os
13
4
 
@@ -72,7 +63,8 @@ class Painter:
72
63
  return
73
64
  path = os.path.join(self.common.get_capture_dir(), '_current.png')
74
65
  if os.path.exists(path):
75
- self.window.ui.painter.image.load(path)
66
+ # load as flat source; layers will be rebuilt on canvas resize
67
+ self.window.ui.painter.load_flat_image(path)
76
68
  else:
77
69
  # clear image
78
70
  self.window.ui.painter.clear_image()
@@ -88,4 +80,4 @@ class Painter:
88
80
 
89
81
  def reload(self):
90
82
  """Reload painter"""
91
- self.setup()
83
+ self.setup()
@@ -11,7 +11,14 @@
11
11
 
12
12
  from PySide6.QtCore import Slot, QTimer
13
13
 
14
- from pygpt_net.core.events import RealtimeEvent, RenderEvent, BaseEvent, AppEvent, KernelEvent
14
+ from pygpt_net.core.events import (
15
+ RealtimeEvent,
16
+ RenderEvent,
17
+ BaseEvent,
18
+ AppEvent,
19
+ KernelEvent,
20
+ Event,
21
+ )
15
22
  from pygpt_net.core.realtime.worker import RealtimeSignals
16
23
  from pygpt_net.core.types import MODE_AUDIO
17
24
  from pygpt_net.utils import trans
@@ -122,10 +129,12 @@ class Realtime:
122
129
  "begin": False,
123
130
  }))
124
131
 
125
- # audio end: stop audio playback
132
+ # audio end: on stop audio playback
126
133
  elif event.name == RealtimeEvent.RT_OUTPUT_AUDIO_END:
127
134
  self.set_idle()
128
135
  self.window.controller.chat.common.unlock_input()
136
+ if self.is_loop():
137
+ QTimer.singleShot(500, lambda: self.next_turn()) # wait a bit before next turn
129
138
 
130
139
  # end of turn: finalize the response
131
140
  elif event.name == RealtimeEvent.RT_OUTPUT_TURN_END:
@@ -163,6 +172,22 @@ class Realtime:
163
172
  elif event.name == AppEvent.CTX_SELECTED:
164
173
  QTimer.singleShot(0, lambda: self.reset())
165
174
 
175
+ def next_turn(self):
176
+ """Start next turn in loop mode (if enabled)"""
177
+ self.window.dispatch(Event(Event.AUDIO_INPUT_RECORD_TOGGLE))
178
+ if self.window.controller.audio.is_recording():
179
+ QTimer.singleShot(100, lambda: self.window.update_status(trans("speech.listening")))
180
+
181
+ def is_loop(self) -> bool:
182
+ """
183
+ Check if loop recording is enabled
184
+
185
+ :return: True if loop recording is enabled, False otherwise
186
+ """
187
+ if self.window.controller.kernel.stopped():
188
+ return False
189
+ return self.window.core.config.get("audio.input.loop", False)
190
+
166
191
  @Slot(object)
167
192
  def handle_response(self, event: RealtimeEvent):
168
193
  """
@@ -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 23:00:00 #
9
+ # Updated Date: 2025.09.01 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.types import (
@@ -60,8 +60,10 @@ class Mode:
60
60
 
61
61
  if not is_audio:
62
62
  self.window.ui.nodes['audio.auto_turn'].setVisible(False)
63
+ self.window.ui.nodes["audio.loop"].setVisible(False)
63
64
  else:
64
65
  self.window.ui.nodes['audio.auto_turn'].setVisible(True)
66
+ self.window.ui.nodes["audio.loop"].setVisible(True)
65
67
 
66
68
  if not is_assistant:
67
69
  ui_nodes['presets.widget'].setVisible(True)
@@ -138,9 +140,21 @@ class Mode:
138
140
  ui_tabs['preset.editor.extra'].setTabText(0, trans("preset.prompt"))
139
141
 
140
142
  if is_image:
141
- ui_nodes['dalle.options'].setVisible(True)
143
+ ui_nodes['media.raw'].setVisible(True)
144
+ if ctrl.media.is_video_model():
145
+ ui_nodes['video.options'].setVisible(True)
146
+ ui_nodes['dalle.options'].setVisible(False)
147
+ elif ctrl.media.is_image_model():
148
+ ui_nodes['dalle.options'].setVisible(True)
149
+ ui_nodes['video.options'].setVisible(False)
150
+ else:
151
+ ui_nodes['media.raw'].setVisible(False)
152
+ ui_nodes['dalle.options'].setVisible(False)
153
+ ui_nodes['video.options'].setVisible(False)
142
154
  else:
155
+ ui_nodes['media.raw'].setVisible(False)
143
156
  ui_nodes['dalle.options'].setVisible(False)
157
+ ui_nodes['video.options'].setVisible(False)
144
158
 
145
159
  if is_agent:
146
160
  ui_nodes['agent.options'].setVisible(True)