pygpt-net 2.7.3__py3-none-any.whl → 2.7.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. pygpt_net/CHANGELOG.txt +8 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +382 -350
  4. pygpt_net/controller/chat/attachment.py +5 -1
  5. pygpt_net/controller/chat/image.py +15 -3
  6. pygpt_net/controller/files/files.py +3 -1
  7. pygpt_net/controller/layout/layout.py +2 -2
  8. pygpt_net/controller/theme/nodes.py +2 -1
  9. pygpt_net/controller/ui/mode.py +5 -1
  10. pygpt_net/controller/ui/ui.py +17 -2
  11. pygpt_net/core/filesystem/url.py +4 -1
  12. pygpt_net/core/render/web/helpers.py +5 -0
  13. pygpt_net/data/config/config.json +3 -4
  14. pygpt_net/data/config/models.json +3 -3
  15. pygpt_net/data/config/settings.json +0 -14
  16. pygpt_net/data/css/web-blocks.css +3 -0
  17. pygpt_net/data/css/web-chatgpt.css +3 -0
  18. pygpt_net/data/locale/locale.de.ini +2 -0
  19. pygpt_net/data/locale/locale.en.ini +3 -1
  20. pygpt_net/data/locale/locale.es.ini +2 -0
  21. pygpt_net/data/locale/locale.fr.ini +2 -0
  22. pygpt_net/data/locale/locale.it.ini +2 -0
  23. pygpt_net/data/locale/locale.pl.ini +2 -0
  24. pygpt_net/data/locale/locale.uk.ini +2 -0
  25. pygpt_net/data/locale/locale.zh.ini +2 -0
  26. pygpt_net/launcher.py +115 -55
  27. pygpt_net/preload.py +243 -0
  28. pygpt_net/provider/api/google/image.py +74 -6
  29. pygpt_net/provider/api/google/video.py +9 -4
  30. pygpt_net/provider/api/openai/image.py +42 -19
  31. pygpt_net/provider/api/openai/video.py +27 -2
  32. pygpt_net/provider/api/x_ai/image.py +25 -2
  33. pygpt_net/provider/core/config/patch.py +7 -0
  34. pygpt_net/ui/layout/chat/input.py +20 -2
  35. pygpt_net/ui/layout/chat/painter.py +6 -4
  36. pygpt_net/ui/layout/toolbox/image.py +5 -5
  37. pygpt_net/ui/layout/toolbox/video.py +5 -4
  38. pygpt_net/ui/main.py +84 -3
  39. pygpt_net/ui/widget/dialog/base.py +3 -10
  40. pygpt_net/ui/widget/option/combo.py +119 -1
  41. pygpt_net/ui/widget/textarea/input_extra.py +664 -0
  42. {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/METADATA +17 -9
  43. {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/RECORD +46 -44
  44. {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/LICENSE +0 -0
  45. {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/WHEEL +0 -0
  46. {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.12.27 21:00:00 #
9
+ # Updated Date: 2025.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -376,10 +376,14 @@ class Attachment(QObject):
376
376
 
377
377
  def show_uploaded(self):
378
378
  """Show uploaded attachments"""
379
+ if self.window.ui.tabs['input'].isTabVisible(self.uploaded_tab_idx):
380
+ return
379
381
  self.window.ui.tabs['input'].setTabVisible(self.uploaded_tab_idx, True)
380
382
 
381
383
  def hide_uploaded(self):
382
384
  """Hide uploaded attachments"""
385
+ if not self.window.ui.tabs['input'].isTabVisible(self.uploaded_tab_idx):
386
+ return
383
387
  self.window.ui.tabs['input'].setTabVisible(self.uploaded_tab_idx, False)
384
388
 
385
389
  def delete_by_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.12.30 22:00:00 #
9
+ # Updated Date: 2025.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -57,6 +57,7 @@ class Image:
57
57
  mode = core.config.get('mode')
58
58
  model = core.config.get('model')
59
59
  model_data = core.models.get(model)
60
+ extra_prompt = self.window.ui.nodes['input_extra'].toPlainText().strip()
60
61
  if (model_data and model_data.id
61
62
  and (model_data.id == 'dall-e-3' or model_data.id.startswith('gpt-image-1'))):
62
63
  num = 1
@@ -140,6 +141,7 @@ class Image:
140
141
  "num": num,
141
142
  "image_id": image_id, # pass previous image_id for variations
142
143
  "video_id": video_id, # pass previous video_id for variations
144
+ "extra_prompt": extra_prompt,
143
145
  },
144
146
  }))
145
147
  except Exception as e:
@@ -173,10 +175,20 @@ class Image:
173
175
  }))
174
176
  string = ""
175
177
  i = 1
178
+ urls = []
179
+ ico_dir = os.path.join("%appdir%", "data", "icons")
180
+ ico_download = os.path.join(ico_dir, "download.svg")
181
+ ico_preview = os.path.join(ico_dir, "view.svg")
176
182
  for path in paths:
177
- basename = os.path.basename(path)
178
- string += f"[{basename}]({path})\n"
183
+ safe_path = self.window.core.filesystem.make_local(path)
184
+ """
185
+ urls.append(f"![image]({ico_preview}) [**{trans('action.preview')}**]({safe_path}) "
186
+ f"![image]({ico_download})[**{trans('action.download')}**](bridge://download/{safe_path})")
187
+ """
188
+ urls.append(f"[**{trans('action.open')}**]({safe_path}) | "
189
+ f"[**{trans('action.download')}**](bridge://download/{safe_path})")
179
190
  i += 1
191
+ string += "\n".join(urls)
180
192
 
181
193
  if not core.config.get('img_raw'):
182
194
  string += f"\nPrompt: {prompt}"
@@ -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.12.27 17:00:00 #
9
+ # Updated Date: 2025.12.31 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
@@ -14,6 +14,7 @@ import os
14
14
  import shutil
15
15
  from typing import Optional, Union
16
16
  from shutil import copy2
17
+ from urllib.parse import unquote
17
18
 
18
19
  from PySide6.QtWidgets import QFileDialog, QApplication
19
20
 
@@ -210,6 +211,7 @@ class Files:
210
211
  for p in path:
211
212
  self.download_local(p)
212
213
  return
214
+ path = self.window.core.filesystem.to_workdir(unquote(path))
213
215
  last_dir = self.window.core.config.get_last_used_dir()
214
216
  dialog = QFileDialog(self.window)
215
217
  dialog.setDirectory(last_dir)
@@ -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.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtWidgets import QApplication
@@ -34,7 +34,7 @@ class Layout:
34
34
  "interpreter.columns",
35
35
  "columns",
36
36
  ]
37
- self.text_nodes = ["input"]
37
+ self.text_nodes = ["input", "input_extra"]
38
38
 
39
39
  def setup(self):
40
40
  """Setup layout"""
@@ -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.24 23:00:00 #
9
+ # Updated Date: 2025.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.events import RenderEvent
@@ -58,6 +58,7 @@ class Nodes:
58
58
  nodes = {
59
59
  'font.chat.input': [
60
60
  'input',
61
+ 'input_extra',
61
62
  ],
62
63
  'font.chat.output': [
63
64
  'output',
@@ -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.12.25 20:00:00 #
9
+ # Updated Date: 2025.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.types import (
@@ -59,6 +59,8 @@ class Mode:
59
59
  is_completion = mode == MODE_COMPLETION
60
60
  is_audio = mode == MODE_AUDIO
61
61
 
62
+ ctrl.ui.hide_input_extra()
63
+
62
64
  # enable/disable system prompt edit - disable in agents (prompts are defined per agent in presets)
63
65
  if not is_agent_openai and not is_agent_llama:
64
66
  presets_editor.toggle_tab("personalize", True)
@@ -178,6 +180,8 @@ class Mode:
178
180
  ui_nodes['media.raw'].setVisible(False)
179
181
  ui_nodes['dalle.options'].setVisible(False)
180
182
  ui_nodes['video.options'].setVisible(False)
183
+
184
+ ctrl.ui.show_input_extra()
181
185
  else:
182
186
  ui_nodes['media.raw'].setVisible(False)
183
187
  ui_nodes['dalle.options'].setVisible(False)
@@ -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.12.25 20:00:00 #
9
+ # Updated Date: 2025.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional
@@ -53,6 +53,7 @@ class UI:
53
53
 
54
54
  # Cache for Input tab tooltip to avoid redundant updates
55
55
  self._last_input_tab_tooltip = None
56
+ self._input_extra_tab_idx = 4
56
57
 
57
58
  def setup(self):
58
59
  """Setup UI"""
@@ -275,4 +276,18 @@ class UI:
275
276
  key="img_mode",
276
277
  option=self.window.core.image.get_mode_option(),
277
278
  value=current,
278
- )
279
+ )
280
+
281
+ def show_input_extra(self):
282
+ """Show extra input area"""
283
+ if self.window.ui.tabs['input'].isTabVisible(self._input_extra_tab_idx):
284
+ return
285
+ self.window.ui.tabs['input'].setTabText(self._input_extra_tab_idx, trans('input.tab.extra.negative_prompt'))
286
+ self.window.ui.tabs['input'].setTabVisible(self._input_extra_tab_idx, True)
287
+
288
+ def hide_input_extra(self):
289
+ """Hide extra input area"""
290
+ if not self.window.ui.tabs['input'].isTabVisible(self._input_extra_tab_idx):
291
+ return
292
+ self.window.ui.tabs['input'].setTabVisible(self._input_extra_tab_idx, False)
293
+ self.window.ui.tabs['input'].setCurrentIndex(0)
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.22 19:00:00 #
9
+ # Updated Date: 2025.12.31 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import QUrl
@@ -47,6 +47,9 @@ class Url:
47
47
  elif url.toString().startswith('bridge://play_video/'):
48
48
  self.window.controller.media.play_video(url.toString().replace("bridge://play_video/", ""))
49
49
  return
50
+ elif url.toString().startswith('bridge://download/'):
51
+ self.window.controller.files.download_local(url.toString().replace("bridge://download/", ""))
52
+ return
50
53
 
51
54
  # -------------
52
55
  extra_schemes = (
@@ -17,6 +17,7 @@ class Helpers:
17
17
 
18
18
  _RE_HTML_ANGLE_OR_MATH = re.compile(r'(\\\[.*?\\\])|(<)|(>)', flags=re.DOTALL)
19
19
  _RE_WORKDIR_TOKEN = re.compile(r'\(%workdir%([^)]+)\)')
20
+ _RE_APPDIR_TOKEN = re.compile(r'\(%appdir%([^)]+)\)')
20
21
 
21
22
  _PLACEHOLDER_THINK_OPEN = "{{{{think}}}}"
22
23
  _PLACEHOLDER_THINK_CLOSE = "{{{{/think}}}}"
@@ -183,6 +184,10 @@ class Helpers:
183
184
  prefix = self.window.core.filesystem.get_workdir_prefix()
184
185
  s = self._RE_WORKDIR_TOKEN.sub(lambda m, p=prefix: f'({p}{m.group(1)})', s)
185
186
 
187
+ if "%appdir%" in s:
188
+ prefix = self.window.core.config.get_app_path()
189
+ s = self._RE_APPDIR_TOKEN.sub(lambda m, p=prefix: f'({p}{m.group(1)})', s)
190
+
186
191
  return s
187
192
 
188
193
  def post_format_text(self, text: str) -> str:
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.7.3",
4
- "app.version": "2.7.3",
5
- "updated_at": "2025-12-30T00:00:00"
3
+ "version": "2.7.4",
4
+ "app.version": "2.7.4",
5
+ "updated_at": "2025-12-31T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -538,7 +538,6 @@
538
538
  "video.duration": 8,
539
539
  "video.fps": 24,
540
540
  "video.generate_audio": false,
541
- "video.negative_prompt": "",
542
541
  "video.player.path": "",
543
542
  "video.player.volume": 100,
544
543
  "video.player.volume.mute": false,
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.7.3",
4
- "app.version": "2.7.3",
5
- "updated_at": "2025-12-30T00:00:00"
3
+ "version": "2.7.4",
4
+ "app.version": "2.7.4",
5
+ "updated_at": "2025-12-31T00:00:00"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -1608,20 +1608,6 @@
1608
1608
  "advanced": false,
1609
1609
  "tab": "video"
1610
1610
  },
1611
- "video.negative_prompt": {
1612
- "section": "images",
1613
- "type": "textarea",
1614
- "slider": false,
1615
- "label": "settings.video.negative_prompt",
1616
- "description": "settings.video.negative_prompt.desc",
1617
- "value": "",
1618
- "min": null,
1619
- "max": null,
1620
- "multiplier": null,
1621
- "step": null,
1622
- "advanced": false,
1623
- "tab": "video"
1624
- },
1625
1611
  "video.prompt_model": {
1626
1612
  "section": "images",
1627
1613
  "type": "combo",
@@ -17,6 +17,9 @@ body {{
17
17
  padding: 0;
18
18
  max-width: 100%;
19
19
  }}
20
+ img {{
21
+ vertical-align: middle;
22
+ }}
20
23
  ::-webkit-scrollbar {{
21
24
  height: 12px;
22
25
  width: 12px;
@@ -18,6 +18,9 @@ body {{
18
18
  max-width: 720px;
19
19
  border-radius: .25rem;
20
20
  }}
21
+ img {{
22
+ vertical-align: middle;
23
+ }}
21
24
  ::-webkit-scrollbar {{
22
25
  height: 12px;
23
26
  width: 12px;
@@ -773,6 +773,8 @@ input.search.placeholder = Suchen...
773
773
  input.send_clear = Nach dem Senden löschen
774
774
  input.stream = Streamen
775
775
  input.tab = Eingabe
776
+ input.tab.extra = Zusätzlicher Prompt
777
+ input.tab.extra.negative_prompt = Negative prompt
776
778
  input.tab.tooltip = {chars} Zeichen (~{tokens} Token)
777
779
  interpreter.all = Verlauf ausführen (alle)
778
780
  interpreter.auto_clear = Bei Senden löschen
@@ -761,7 +761,7 @@ idx.status.truncating = Removing index... please wait...
761
761
  idx.token.warn = This will consume additional tokens to embed the data.
762
762
  img.action.open = Open full size
763
763
  img.action.save = Save As...
764
- img.raw = Raw mode
764
+ img.raw = Raw
765
765
  img.remix = Remix/Extend
766
766
  img.remix.tooltip = Enable remix/extend from the previous image in context.\nIf enabled, the previous image will be used as a reference instead of creating a new one from scratch.
767
767
  img.save.title = Save image
@@ -787,6 +787,8 @@ input.search.placeholder = Search...
787
787
  input.send_clear = Clear on send
788
788
  input.stream = Stream
789
789
  input.tab = Input
790
+ input.tab.extra = Extra Input
791
+ input.tab.extra.negative_prompt = Negative prompt
790
792
  input.tab.tooltip = {chars} chars (~{tokens} tokens)
791
793
  interpreter.all = Execute history (all)
792
794
  interpreter.auto_clear = Clear output on send
@@ -774,6 +774,8 @@ input.search.placeholder = Buscar...
774
774
  input.send_clear = Limpiar al enviar
775
775
  input.stream = Transmisión
776
776
  input.tab = Entrada
777
+ input.tab.extra = Prompt adicional
778
+ input.tab.extra.negative_prompt = Negative prompt
777
779
  input.tab.tooltip = {chars} caracteres (~{tokens} fichas)
778
780
  interpreter.all = Ejecutar historial (todo)
779
781
  interpreter.auto_clear = Limpiar al enviar
@@ -773,6 +773,8 @@ input.search.placeholder = Rechercher...
773
773
  input.send_clear = Effacer après envoi
774
774
  input.stream = Flux
775
775
  input.tab = Entrée
776
+ input.tab.extra = Prompt supplémentaire
777
+ input.tab.extra.negative_prompt = Negative prompt
776
778
  input.tab.tooltip = {chars} caractères (~{tokens} jetons)
777
779
  interpreter.all = Exécuter l'historique (tous)
778
780
  interpreter.auto_clear = Effacer à l'envoi
@@ -773,6 +773,8 @@ input.search.placeholder = Cerca...
773
773
  input.send_clear = Pulisci dopo l'invio
774
774
  input.stream = Flusso
775
775
  input.tab = Input
776
+ input.tab.extra = Prompt extra
777
+ input.tab.extra.negative_prompt = Negative prompt
776
778
  input.tab.tooltip = {chars} caratteri (~{tokens} token)
777
779
  interpreter.all = Esegui cronologia (tutto)
778
780
  interpreter.auto_clear = Cancella all'invio
@@ -774,6 +774,8 @@ input.search.placeholder = Szukaj...
774
774
  input.send_clear = Wyczyść po wysłaniu
775
775
  input.stream = Stream
776
776
  input.tab = Input
777
+ input.tab.extra = Dodatkowy prompt
778
+ input.tab.extra.negative_prompt = Negative prompt
777
779
  input.tab.tooltip = {chars} znaków (~{tokens} tokenów)
778
780
  interpreter.all = Wykonaj historię (wszystko)
779
781
  interpreter.auto_clear = Wyczyść wyjście przy wysyłaniu
@@ -773,6 +773,8 @@ input.search.placeholder = Пошук...
773
773
  input.send_clear = Очистити після відправлення
774
774
  input.stream = Потік
775
775
  input.tab = Введення
776
+ input.tab.extra = Додатковий prompt
777
+ input.tab.extra.negative_prompt = Negative prompt
776
778
  input.tab.tooltip = {chars} символів (~{tokens} токенів)
777
779
  interpreter.all = Виконати історію (все)
778
780
  interpreter.auto_clear = Очистити при відправці
@@ -773,6 +773,8 @@ input.search.placeholder = 搜索...
773
773
  input.send_clear = 發送後清除
774
774
  input.stream = 流
775
775
  input.tab = 輸入
776
+ input.tab.extra = 额外提示
777
+ input.tab.extra.negative_prompt = Negative prompt
776
778
  input.tab.tooltip = {chars} 字符 (~{tokens} 令牌)
777
779
  interpreter.all = 执行历史记录(全部)
778
780
  interpreter.auto_clear = 发送时清除
pygpt_net/launcher.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.19 07:00:00 #
9
+ # Updated Date: 2025.12.31 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -50,6 +50,31 @@ class Launcher:
50
50
  self.force_disable_gpu = False
51
51
  self.shortcut_filter = None
52
52
  self.workdir = None
53
+ self._preloader = None
54
+
55
+ @staticmethod
56
+ def _clean_multiprocessing_argv(argv):
57
+ """
58
+ Clear multiprocessing flags from argv
59
+
60
+ :param argv: list of command line arguments
61
+ """
62
+ skip_flags = {
63
+ "--multiprocessing-fork",
64
+ "--multiprocessing-spawn",
65
+ "--billiard-fork",
66
+ "--billiard-spawn",
67
+ }
68
+ skip_prefixes = (
69
+ "parent_pid=",
70
+ "pipe_handle=",
71
+ "forkserver_port=",
72
+ "forkserver_authkey=",
73
+ )
74
+ return [
75
+ a for a in argv
76
+ if a not in skip_flags and not any(a.startswith(p) for p in skip_prefixes)
77
+ ]
53
78
 
54
79
  def setup(self) -> dict:
55
80
  """
@@ -57,59 +82,65 @@ class Launcher:
57
82
 
58
83
  :return: dict with launcher arguments
59
84
  """
60
- parser = argparse.ArgumentParser()
61
- parser.add_argument(
62
- "-d",
63
- "--debug",
64
- required=False,
65
- help="debug mode (0=disabled, 1=info, 2=debug)",
66
- )
67
- parser.add_argument(
68
- "-l",
69
- "--legacy",
70
- required=False,
71
- help="force enable legacy mode (0=disabled, 1=enable)",
72
- )
73
- parser.add_argument(
74
- "-n",
75
- "--disable-gpu",
76
- required=False,
77
- help="force disable OpenGL (1=disabled, 0=enabled)",
78
- )
79
- parser.add_argument(
80
- "-w",
81
- "--workdir",
82
- required=False,
83
- help="force set workdir",
84
- )
85
- args = vars(parser.parse_args())
86
-
87
- # set log level [ERROR|WARNING|INFO|DEBUG]
88
- if "debug" in args and args["debug"] == "1":
89
- print("** Debug mode enabled (1=INFO)")
90
- Debug.init(INFO)
91
- self.debug = True
92
- elif "debug" in args and args["debug"] == "2":
93
- print("** Debug mode enabled (2=DEBUG)")
94
- Debug.init(DEBUG)
95
- self.debug = True
96
- else:
97
- Debug.init(ERROR) # default log level
98
-
99
- # force legacy mode
100
- if "legacy" in args and args["legacy"] == "1":
101
- print("** Force legacy mode enabled")
102
- self.force_legacy = True
103
-
104
- # force disable GPU
105
- if "disable-gpu" in args and args["disable-gpu"] == "1":
106
- print("** Force disable GPU enabled")
107
- self.force_disable_gpu = True
108
-
109
- # force set workdir
110
- if "workdir" in args and args["workdir"] is not None:
111
- # set as environment variable
112
- os.environ["PYGPT_WORKDIR"] = args["workdir"]
85
+ args = {}
86
+ try:
87
+ parser = argparse.ArgumentParser()
88
+ parser.add_argument(
89
+ "-d",
90
+ "--debug",
91
+ required=False,
92
+ help="debug mode (0=disabled, 1=info, 2=debug)",
93
+ )
94
+ parser.add_argument(
95
+ "-l",
96
+ "--legacy",
97
+ required=False,
98
+ help="force enable legacy mode (0=disabled, 1=enable)",
99
+ )
100
+ parser.add_argument(
101
+ "-n",
102
+ "--disable-gpu",
103
+ required=False,
104
+ help="force disable OpenGL (1=disabled, 0=enabled)",
105
+ )
106
+ parser.add_argument(
107
+ "-w",
108
+ "--workdir",
109
+ required=False,
110
+ help="force set workdir",
111
+ )
112
+ safe_argv = self._clean_multiprocessing_argv(sys.argv[1:])
113
+ known, unknown = parser.parse_known_args(safe_argv)
114
+ args = vars(known)
115
+
116
+ # set log level [ERROR|WARNING|INFO|DEBUG]
117
+ if "debug" in args and args["debug"] == "1":
118
+ print("** Debug mode enabled (1=INFO)")
119
+ Debug.init(INFO)
120
+ self.debug = True
121
+ elif "debug" in args and args["debug"] == "2":
122
+ print("** Debug mode enabled (2=DEBUG)")
123
+ Debug.init(DEBUG)
124
+ self.debug = True
125
+ else:
126
+ Debug.init(ERROR) # default log level
127
+
128
+ # force legacy mode
129
+ if "legacy" in args and args["legacy"] == "1":
130
+ print("** Force legacy mode enabled")
131
+ self.force_legacy = True
132
+
133
+ # force disable GPU
134
+ if "disable_gpu" in args and args["disable_gpu"] == "1":
135
+ print("** Force disable GPU enabled")
136
+ self.force_disable_gpu = True
137
+
138
+ # force set workdir
139
+ if "workdir" in args and args["workdir"] is not None:
140
+ # set as environment variable
141
+ os.environ["PYGPT_WORKDIR"] = args["workdir"]
142
+ except Exception as e:
143
+ print(f"** Launcher setup error: {e}")
113
144
 
114
145
  return args
115
146
 
@@ -123,6 +154,13 @@ class Launcher:
123
154
  self.window = MainWindow(self.app, args=args)
124
155
  self.shortcut_filter = GlobalShortcutFilter(self.window)
125
156
 
157
+ # Connect the window "ready" signal to close the splash
158
+ if self._preloader is not None:
159
+ try:
160
+ self.window.appReady.connect(self._on_window_ready, Qt.QueuedConnection)
161
+ except Exception:
162
+ pass
163
+
126
164
  def handle_signal(self, signal_number, frame):
127
165
  """
128
166
  Handle termination signal (SIGTERM, SIGINT)
@@ -136,6 +174,28 @@ class Launcher:
136
174
  print("Shutting down...")
137
175
  self.window.close()
138
176
 
177
+ def attach_preloader(self, preloader):
178
+ """
179
+ Attach external splash controller. The splash will be closed
180
+ when the main window announces readiness.
181
+ """
182
+ self._preloader = preloader
183
+ if self.window is not None:
184
+ try:
185
+ self.window.appReady.connect(self._on_window_ready, Qt.QueuedConnection)
186
+ except Exception:
187
+ pass
188
+
189
+ def _on_window_ready(self):
190
+ """
191
+ Close preloader when the first frame of the main window is ready.
192
+ """
193
+ if self._preloader:
194
+ try:
195
+ self._preloader.close()
196
+ except Exception:
197
+ pass
198
+ self._preloader = None
139
199
 
140
200
  def add_plugin(self, plugin: BasePlugin):
141
201
  """
@@ -290,4 +350,4 @@ class Launcher:
290
350
  # self.window.core.debug.mem("INIT") # debug memory usage
291
351
  signal.signal(signal.SIGTERM, self.handle_signal)
292
352
  signal.signal(signal.SIGINT, self.handle_signal)
293
- sys.exit(self.app.exec())
353
+ sys.exit(self.app.exec())