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.
- pygpt_net/CHANGELOG.txt +8 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +382 -350
- pygpt_net/controller/chat/attachment.py +5 -1
- pygpt_net/controller/chat/image.py +15 -3
- pygpt_net/controller/files/files.py +3 -1
- pygpt_net/controller/layout/layout.py +2 -2
- pygpt_net/controller/theme/nodes.py +2 -1
- pygpt_net/controller/ui/mode.py +5 -1
- pygpt_net/controller/ui/ui.py +17 -2
- pygpt_net/core/filesystem/url.py +4 -1
- pygpt_net/core/render/web/helpers.py +5 -0
- pygpt_net/data/config/config.json +3 -4
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +0 -14
- pygpt_net/data/css/web-blocks.css +3 -0
- pygpt_net/data/css/web-chatgpt.css +3 -0
- pygpt_net/data/locale/locale.de.ini +2 -0
- pygpt_net/data/locale/locale.en.ini +3 -1
- pygpt_net/data/locale/locale.es.ini +2 -0
- pygpt_net/data/locale/locale.fr.ini +2 -0
- pygpt_net/data/locale/locale.it.ini +2 -0
- pygpt_net/data/locale/locale.pl.ini +2 -0
- pygpt_net/data/locale/locale.uk.ini +2 -0
- pygpt_net/data/locale/locale.zh.ini +2 -0
- pygpt_net/launcher.py +115 -55
- pygpt_net/preload.py +243 -0
- pygpt_net/provider/api/google/image.py +74 -6
- pygpt_net/provider/api/google/video.py +9 -4
- pygpt_net/provider/api/openai/image.py +42 -19
- pygpt_net/provider/api/openai/video.py +27 -2
- pygpt_net/provider/api/x_ai/image.py +25 -2
- pygpt_net/provider/core/config/patch.py +7 -0
- pygpt_net/ui/layout/chat/input.py +20 -2
- pygpt_net/ui/layout/chat/painter.py +6 -4
- pygpt_net/ui/layout/toolbox/image.py +5 -5
- pygpt_net/ui/layout/toolbox/video.py +5 -4
- pygpt_net/ui/main.py +84 -3
- pygpt_net/ui/widget/dialog/base.py +3 -10
- pygpt_net/ui/widget/option/combo.py +119 -1
- pygpt_net/ui/widget/textarea/input_extra.py +664 -0
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/METADATA +17 -9
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/RECORD +46 -44
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.4.dist-info}/WHEEL +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
178
|
-
|
|
183
|
+
safe_path = self.window.core.filesystem.make_local(path)
|
|
184
|
+
"""
|
|
185
|
+
urls.append(f" [**{trans('action.preview')}**]({safe_path}) "
|
|
186
|
+
f"[**{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.
|
|
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.
|
|
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.
|
|
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',
|
pygpt_net/controller/ui/mode.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.12.
|
|
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)
|
pygpt_net/controller/ui/ui.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.12.
|
|
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)
|
pygpt_net/core/filesystem/url.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.
|
|
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.
|
|
4
|
-
"app.version": "2.7.
|
|
5
|
-
"updated_at": "2025-12-
|
|
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,
|
|
@@ -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",
|
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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())
|