pygpt-net 2.7.2__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 (48) hide show
  1. pygpt_net/CHANGELOG.txt +12 -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 +40 -5
  6. pygpt_net/controller/files/files.py +3 -1
  7. pygpt_net/controller/layout/layout.py +2 -2
  8. pygpt_net/controller/media/media.py +70 -1
  9. pygpt_net/controller/theme/nodes.py +2 -1
  10. pygpt_net/controller/ui/mode.py +5 -1
  11. pygpt_net/controller/ui/ui.py +17 -2
  12. pygpt_net/core/filesystem/url.py +4 -1
  13. pygpt_net/core/render/web/helpers.py +5 -0
  14. pygpt_net/data/config/config.json +5 -4
  15. pygpt_net/data/config/models.json +3 -3
  16. pygpt_net/data/config/settings.json +0 -14
  17. pygpt_net/data/css/web-blocks.css +3 -0
  18. pygpt_net/data/css/web-chatgpt.css +3 -0
  19. pygpt_net/data/locale/locale.de.ini +6 -0
  20. pygpt_net/data/locale/locale.en.ini +7 -1
  21. pygpt_net/data/locale/locale.es.ini +6 -0
  22. pygpt_net/data/locale/locale.fr.ini +6 -0
  23. pygpt_net/data/locale/locale.it.ini +6 -0
  24. pygpt_net/data/locale/locale.pl.ini +7 -1
  25. pygpt_net/data/locale/locale.uk.ini +6 -0
  26. pygpt_net/data/locale/locale.zh.ini +6 -0
  27. pygpt_net/launcher.py +115 -55
  28. pygpt_net/preload.py +243 -0
  29. pygpt_net/provider/api/google/image.py +317 -10
  30. pygpt_net/provider/api/google/video.py +160 -4
  31. pygpt_net/provider/api/openai/image.py +201 -93
  32. pygpt_net/provider/api/openai/video.py +99 -24
  33. pygpt_net/provider/api/x_ai/image.py +25 -2
  34. pygpt_net/provider/core/config/patch.py +17 -1
  35. pygpt_net/ui/layout/chat/input.py +20 -2
  36. pygpt_net/ui/layout/chat/painter.py +6 -4
  37. pygpt_net/ui/layout/toolbox/image.py +21 -11
  38. pygpt_net/ui/layout/toolbox/raw.py +2 -2
  39. pygpt_net/ui/layout/toolbox/video.py +22 -9
  40. pygpt_net/ui/main.py +84 -3
  41. pygpt_net/ui/widget/dialog/base.py +3 -10
  42. pygpt_net/ui/widget/option/combo.py +119 -1
  43. pygpt_net/ui/widget/textarea/input_extra.py +664 -0
  44. {pygpt_net-2.7.2.dist-info → pygpt_net-2.7.4.dist-info}/METADATA +27 -20
  45. {pygpt_net-2.7.2.dist-info → pygpt_net-2.7.4.dist-info}/RECORD +48 -46
  46. {pygpt_net-2.7.2.dist-info → pygpt_net-2.7.4.dist-info}/LICENSE +0 -0
  47. {pygpt_net-2.7.2.dist-info → pygpt_net-2.7.4.dist-info}/WHEEL +0 -0
  48. {pygpt_net-2.7.2.dist-info → pygpt_net-2.7.4.dist-info}/entry_points.txt +0 -0
@@ -748,6 +748,8 @@ idx.token.warn = Це призведе до використання додат
748
748
  img.action.open = Відкрити повний розмір
749
749
  img.action.save = Зберегти як...
750
750
  img.raw = Режим сирця
751
+ img.remix = Ремікс/Розширити
752
+ img.remix.tooltip = Увімкнути ремікс/розширення з попереднього зображення у контексті.\nЯкщо увімкнено, попереднє зображення буде використовуватися як довідник замість створення нового з нуля.
751
753
  img.save.title = Зберегти зображення
752
754
  img.status.downloading = Завантаження...
753
755
  img.status.error = Помилка генерації зображення
@@ -771,6 +773,8 @@ input.search.placeholder = Пошук...
771
773
  input.send_clear = Очистити після відправлення
772
774
  input.stream = Потік
773
775
  input.tab = Введення
776
+ input.tab.extra = Додатковий prompt
777
+ input.tab.extra.negative_prompt = Negative prompt
774
778
  input.tab.tooltip = {chars} символів (~{tokens} токенів)
775
779
  interpreter.all = Виконати історію (все)
776
780
  interpreter.auto_clear = Очистити при відправці
@@ -1677,6 +1681,8 @@ updater.check.launch = Перевіряти при запуску
1677
1681
  update.released = збірка
1678
1682
  update.snap = Перейти до Snap Store
1679
1683
  update.title = Перевірка оновлень
1684
+ video.remix = Ремікс/Розширити
1685
+ video.remix.tooltip = Увімкнути ремікс/розширення з попереднього відео у контексті (Sora2, Veo3.1).\nЯкщо увімкнено, попереднє відео буде використовуватися як довідник замість створення нового з нуля.
1680
1686
  vid.status.downloading = Завантаження відео... будь ласка, зачекайте...
1681
1687
  vid.status.generating = Генерація відео з
1682
1688
  vid.status.prompt.error = Виникла помилка під час покращення запиту
@@ -748,6 +748,8 @@ idx.token.warn = 这将消耗额外的 token 来嵌入数据。
748
748
  img.action.open = 打开全尺寸
749
749
  img.action.save = 另存为...
750
750
  img.raw = 原始模式
751
+ img.remix = 混音/扩展
752
+ img.remix.tooltip = 启用上下文中从上一个图像的混音/扩展。\n如果启用,将使用上一个图像作为参考,而不是从头创建一个新图像。
751
753
  img.save.title = 保存图片
752
754
  img.status.downloading = 正在下载...
753
755
  img.status.error = 图像生成错误
@@ -771,6 +773,8 @@ input.search.placeholder = 搜索...
771
773
  input.send_clear = 發送後清除
772
774
  input.stream = 流
773
775
  input.tab = 輸入
776
+ input.tab.extra = 额外提示
777
+ input.tab.extra.negative_prompt = Negative prompt
774
778
  input.tab.tooltip = {chars} 字符 (~{tokens} 令牌)
775
779
  interpreter.all = 执行历史记录(全部)
776
780
  interpreter.auto_clear = 发送时清除
@@ -1677,6 +1681,8 @@ updater.check.launch = 啟動時檢查
1677
1681
  update.released = 發布時間
1678
1682
  update.snap = 前往Snap商店
1679
1683
  update.title = 檢查更新中
1684
+ video.remix = 混音/扩展
1685
+ video.remix.tooltip = 启用上下文中从上一个视频的混音/扩展 (Sora2, Veo3.1)。\n如果启用,将使用上一个视频作为参考,而不是从头创建一个新视频。
1680
1686
  vid.status.downloading = 正在下载视频...请稍候...
1681
1687
  vid.status.generating = 正在从中生成视频
1682
1688
  vid.status.prompt.error = 提示增强出错
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())
pygpt_net/preload.py ADDED
@@ -0,0 +1,243 @@
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.12.31 17:00:00 #
10
+ # ================================================== #
11
+
12
+ # -------------------------------------------------- #
13
+ # Lightweight splash window (separate process)
14
+ # -------------------------------------------------- #
15
+ def _splash_main(conn, title="PyGPT", message="Loading…"):
16
+ """
17
+ Minimal splash process using PySide6. Runs its own event loop and
18
+ listens for commands on a Pipe: {"type": "msg", "text": "..."} or {"type": "quit"}.
19
+ """
20
+ try:
21
+ # Import locally to keep the main process import path untouched
22
+ from PySide6 import QtCore, QtWidgets
23
+ except Exception:
24
+ return
25
+
26
+ try:
27
+ # Enable HiDPI (safe defaults)
28
+ try:
29
+ QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
30
+ QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
31
+ except Exception:
32
+ pass
33
+
34
+ app = QtWidgets.QApplication(["pygpt_splash"])
35
+
36
+ # Root window styled as splash
37
+ root = QtWidgets.QWidget(
38
+ None,
39
+ QtCore.Qt.SplashScreen
40
+ | QtCore.Qt.FramelessWindowHint
41
+ | QtCore.Qt.WindowStaysOnTopHint
42
+ )
43
+ root.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
44
+ root.setObjectName("SplashRoot")
45
+
46
+ panel = QtWidgets.QFrame(root)
47
+ panel.setObjectName("SplashPanel")
48
+ panel.setStyleSheet("""
49
+ #SplashPanel {
50
+ background-color: rgba(30, 30, 30, 230);
51
+ border-radius: 12px;
52
+ }
53
+ QLabel { color: #ffffff; }
54
+ """)
55
+ layout = QtWidgets.QVBoxLayout(panel)
56
+ layout.setContentsMargins(16, 16, 16, 16)
57
+ layout.setSpacing(8)
58
+
59
+ lbl_title = QtWidgets.QLabel(title, panel)
60
+ lbl_title.setAlignment(QtCore.Qt.AlignCenter)
61
+ lbl_title.setStyleSheet("font-size: 16px; font-weight: 600;")
62
+
63
+ lbl_wait = QtWidgets.QLabel("Initializing...")
64
+ lbl_wait.setAlignment(QtCore.Qt.AlignCenter)
65
+ lbl_wait.setStyleSheet("font-size: 12px;")
66
+
67
+ lbl_msg = QtWidgets.QLabel(message, panel)
68
+ lbl_msg.setAlignment(QtCore.Qt.AlignCenter)
69
+ lbl_msg.setStyleSheet("font-size: 12px;")
70
+
71
+ bar = QtWidgets.QProgressBar(panel)
72
+ bar.setRange(0, 0)
73
+ bar.setTextVisible(False)
74
+ bar.setFixedHeight(8)
75
+ bar.setStyleSheet("QProgressBar { border: 0px; border-radius: 4px; } "
76
+ "QProgressBar::chunk { background-color: #3f3f3f; }")
77
+
78
+ layout.addWidget(lbl_title)
79
+ layout.addWidget(lbl_msg)
80
+ layout.addWidget(bar)
81
+ layout.addWidget(lbl_wait)
82
+
83
+ panel.setFixedSize(360, 120)
84
+ panel.move(0, 0)
85
+ root.resize(panel.size())
86
+
87
+ # Center on primary screen
88
+ screen = app.primaryScreen()
89
+ if screen:
90
+ geo = screen.availableGeometry()
91
+ root.move(geo.center() - root.rect().center())
92
+
93
+ # Ensure initial transparency for fade-in
94
+ try:
95
+ root.setWindowOpacity(0.0)
96
+ except Exception:
97
+ pass
98
+
99
+ root.show()
100
+
101
+ # Fade-in on start (non-blocking, resilient to platform limitations)
102
+ try:
103
+ def _start_fade_in():
104
+ try:
105
+ anim = QtCore.QPropertyAnimation(root, b"windowOpacity")
106
+ anim.setDuration(300)
107
+ anim.setStartValue(0.0)
108
+ anim.setEndValue(1.0)
109
+ root._fade_in_anim = anim # keep reference to avoid GC
110
+ anim.start()
111
+ except Exception:
112
+ try:
113
+ root.setWindowOpacity(1.0)
114
+ except Exception:
115
+ pass
116
+
117
+ QtCore.QTimer.singleShot(0, _start_fade_in)
118
+ except Exception:
119
+ try:
120
+ root.setWindowOpacity(1.0)
121
+ except Exception:
122
+ pass
123
+
124
+ # Poll the pipe for messages and close requests
125
+ timer = QtCore.QTimer()
126
+ timer.setInterval(80)
127
+
128
+ def poll():
129
+ try:
130
+ if conn.poll():
131
+ msg = conn.recv()
132
+ if isinstance(msg, dict):
133
+ t = msg.get("type")
134
+ if t == "quit":
135
+ # Stop fade-in if it is running to avoid conflicting animations
136
+ try:
137
+ if hasattr(root, "_fade_in_anim") and root._fade_in_anim is not None:
138
+ root._fade_in_anim.stop()
139
+ except Exception:
140
+ pass
141
+ # Fade-out and quit
142
+ try:
143
+ anim = QtCore.QPropertyAnimation(root, b"windowOpacity")
144
+ anim.setDuration(180)
145
+ anim.setStartValue(root.windowOpacity())
146
+ anim.setEndValue(0.0)
147
+ anim.finished.connect(app.quit)
148
+ # Keep reference to avoid GC
149
+ root._fade_anim = anim
150
+ anim.start()
151
+ except Exception:
152
+ app.quit()
153
+ elif t == "msg":
154
+ text = msg.get("text", "")
155
+ if text:
156
+ lbl_msg.setText(text)
157
+ elif isinstance(msg, str):
158
+ if msg.lower() == "quit":
159
+ app.quit()
160
+ except (EOFError, OSError):
161
+ # Parent died or pipe closed: exit
162
+ app.quit()
163
+
164
+ timer.timeout.connect(poll)
165
+ timer.start()
166
+
167
+ # Failsafe timeout (can be overridden via env)
168
+ import os as _os
169
+ timeout_ms = int(_os.environ.get("PYGPT_SPLASH_TIMEOUT", "120000"))
170
+ killer = QtCore.QTimer()
171
+ killer.setSingleShot(True)
172
+ killer.timeout.connect(app.quit)
173
+ killer.start(timeout_ms)
174
+
175
+ app.exec()
176
+ except Exception:
177
+ # No crash propagation to main app
178
+ pass
179
+
180
+
181
+ class _Preloader:
182
+ """
183
+ Controller for the splash subprocess.
184
+ """
185
+
186
+ def __init__(self, proc, conn):
187
+ self._proc = proc
188
+ self._conn = conn
189
+
190
+ def set_message(self, text):
191
+ try:
192
+ if self._conn:
193
+ self._conn.send({"type": "msg", "text": str(text)})
194
+ except Exception:
195
+ pass
196
+
197
+ def close(self, wait=True, timeout=2.0):
198
+ try:
199
+ if self._conn:
200
+ try:
201
+ self._conn.send({"type": "quit"})
202
+ except Exception:
203
+ pass
204
+ finally:
205
+ if wait and self._proc is not None:
206
+ self._proc.join(timeout=timeout)
207
+ if self._proc is not None and self._proc.is_alive():
208
+ try:
209
+ self._proc.terminate()
210
+ except Exception:
211
+ pass
212
+ self._conn = None
213
+ self._proc = None
214
+
215
+
216
+ def _start_preloader(title="PyGPT", message="Loading…"):
217
+ """
218
+ Start splash as a separate process using 'spawn' on every OS.
219
+ Returns a _Preloader controller or None if failed.
220
+ """
221
+ try:
222
+ import multiprocessing as mp
223
+ try:
224
+ ctx = mp.get_context("spawn")
225
+ except ValueError:
226
+ ctx = mp
227
+
228
+ parent_conn, child_conn = ctx.Pipe(duplex=True)
229
+ proc = ctx.Process(
230
+ target=_splash_main,
231
+ args=(child_conn, title, message),
232
+ daemon=True
233
+ )
234
+ proc.start()
235
+
236
+ try:
237
+ child_conn.close()
238
+ except Exception:
239
+ pass
240
+
241
+ return _Preloader(proc, parent_conn)
242
+ except Exception:
243
+ return None