pygpt-net 2.7.6__py3-none-any.whl → 2.7.8__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 +13 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +5 -1
- pygpt_net/controller/assistant/batch.py +2 -2
- pygpt_net/controller/assistant/files.py +7 -6
- pygpt_net/controller/assistant/threads.py +0 -0
- pygpt_net/controller/chat/command.py +0 -0
- pygpt_net/controller/chat/remote_tools.py +3 -9
- pygpt_net/controller/chat/stream.py +2 -2
- pygpt_net/controller/chat/{handler/worker.py → stream_worker.py} +13 -35
- pygpt_net/controller/dialogs/confirm.py +35 -58
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
- pygpt_net/controller/remote_store/remote_store.py +982 -13
- pygpt_net/core/command/command.py +0 -0
- pygpt_net/core/db/viewer.py +1 -1
- pygpt_net/core/debug/models.py +2 -2
- pygpt_net/core/realtime/worker.py +3 -1
- pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
- pygpt_net/core/remote_store/anthropic/files.py +211 -0
- pygpt_net/core/remote_store/anthropic/store.py +208 -0
- pygpt_net/core/remote_store/openai/store.py +5 -4
- pygpt_net/core/remote_store/remote_store.py +5 -1
- pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
- pygpt_net/core/remote_store/xai/files.py +225 -0
- pygpt_net/core/remote_store/xai/store.py +219 -0
- pygpt_net/data/config/config.json +18 -5
- pygpt_net/data/config/models.json +193 -4
- pygpt_net/data/config/settings.json +179 -36
- pygpt_net/data/icons/folder_eye.svg +1 -0
- pygpt_net/data/icons/folder_eye_filled.svg +1 -0
- pygpt_net/data/icons/folder_open.svg +1 -0
- pygpt_net/data/icons/folder_open_filled.svg +1 -0
- pygpt_net/data/locale/locale.de.ini +6 -3
- pygpt_net/data/locale/locale.en.ini +46 -12
- pygpt_net/data/locale/locale.es.ini +6 -3
- pygpt_net/data/locale/locale.fr.ini +6 -3
- pygpt_net/data/locale/locale.it.ini +6 -3
- pygpt_net/data/locale/locale.pl.ini +7 -4
- pygpt_net/data/locale/locale.uk.ini +6 -3
- pygpt_net/data/locale/locale.zh.ini +6 -3
- pygpt_net/icons.qrc +4 -0
- pygpt_net/icons_rc.py +282 -138
- pygpt_net/plugin/cmd_mouse_control/worker.py +2 -1
- pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +2 -1
- pygpt_net/provider/api/anthropic/__init__.py +10 -3
- pygpt_net/provider/api/anthropic/chat.py +342 -11
- pygpt_net/provider/api/anthropic/computer.py +844 -0
- pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
- pygpt_net/provider/api/anthropic/store.py +307 -0
- pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +99 -10
- pygpt_net/provider/api/anthropic/tools.py +32 -77
- pygpt_net/provider/api/anthropic/utils.py +30 -0
- pygpt_net/{controller/chat/handler → provider/api/anthropic/worker}/__init__.py +0 -0
- pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
- pygpt_net/provider/api/google/chat.py +62 -9
- pygpt_net/provider/api/google/store.py +124 -3
- pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +92 -25
- pygpt_net/provider/api/google/utils.py +185 -0
- pygpt_net/provider/api/google/worker/importer.py +16 -28
- pygpt_net/provider/api/langchain/__init__.py +0 -0
- pygpt_net/{controller/chat/handler/langchain_stream.py → provider/api/langchain/stream.py} +1 -1
- pygpt_net/provider/api/llama_index/__init__.py +0 -0
- pygpt_net/{controller/chat/handler/llamaindex_stream.py → provider/api/llama_index/stream.py} +1 -1
- pygpt_net/provider/api/openai/assistants.py +2 -2
- pygpt_net/provider/api/openai/image.py +2 -2
- pygpt_net/provider/api/openai/store.py +4 -1
- pygpt_net/{controller/chat/handler/openai_stream.py → provider/api/openai/stream.py} +1 -1
- pygpt_net/provider/api/openai/utils.py +69 -3
- pygpt_net/provider/api/openai/worker/importer.py +19 -61
- pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
- pygpt_net/provider/api/x_ai/__init__.py +138 -15
- pygpt_net/provider/api/x_ai/audio.py +43 -11
- pygpt_net/provider/api/x_ai/chat.py +92 -4
- pygpt_net/provider/api/x_ai/image.py +149 -47
- pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
- pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
- pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
- pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +183 -70
- pygpt_net/provider/api/x_ai/responses.py +507 -0
- pygpt_net/provider/api/x_ai/store.py +610 -0
- pygpt_net/{controller/chat/handler/xai_stream.py → provider/api/x_ai/stream.py} +42 -10
- pygpt_net/provider/api/x_ai/tools.py +59 -8
- pygpt_net/{controller/chat/handler → provider/api/x_ai}/utils.py +1 -2
- pygpt_net/provider/api/x_ai/vision.py +1 -4
- pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
- pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
- pygpt_net/provider/audio_output/xai_tts.py +325 -0
- pygpt_net/provider/core/config/patch.py +39 -3
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
- pygpt_net/provider/core/model/patch.py +39 -1
- pygpt_net/tools/image_viewer/tool.py +334 -34
- pygpt_net/tools/image_viewer/ui/dialogs.py +319 -22
- pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
- pygpt_net/tools/text_editor/ui/widgets.py +0 -0
- pygpt_net/ui/dialog/assistant.py +1 -1
- pygpt_net/ui/dialog/plugins.py +13 -5
- pygpt_net/ui/dialog/remote_store.py +552 -0
- pygpt_net/ui/dialogs.py +3 -5
- pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
- pygpt_net/ui/menu/tools.py +6 -13
- pygpt_net/ui/widget/dialog/base.py +16 -5
- pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +2 -2
- pygpt_net/ui/widget/lists/context.py +2 -2
- pygpt_net/ui/widget/textarea/editor.py +0 -0
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +15 -2
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +107 -89
- pygpt_net/controller/remote_store/google/store.py +0 -615
- pygpt_net/controller/remote_store/openai/batch.py +0 -524
- pygpt_net/controller/remote_store/openai/store.py +0 -699
- pygpt_net/ui/dialog/remote_store_google.py +0 -539
- pygpt_net/ui/dialog/remote_store_openai.py +0 -539
- pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
- pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
- pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# dialog.py
|
|
2
|
+
|
|
3
|
+
# dialog.py
|
|
4
|
+
|
|
1
5
|
#!/usr/bin/env python3
|
|
2
6
|
# -*- coding: utf-8 -*-
|
|
3
7
|
# ================================================== #
|
|
@@ -6,12 +10,24 @@
|
|
|
6
10
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
11
|
# MIT License #
|
|
8
12
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2026.01.
|
|
13
|
+
# Updated Date: 2026.01.06 19:00:00 #
|
|
10
14
|
# ================================================== #
|
|
11
15
|
|
|
12
16
|
from PySide6.QtCore import Qt, QPoint, QSize, QEvent
|
|
13
17
|
from PySide6.QtGui import QAction, QIcon
|
|
14
|
-
from PySide6.QtWidgets import
|
|
18
|
+
from PySide6.QtWidgets import (
|
|
19
|
+
QMenuBar,
|
|
20
|
+
QVBoxLayout,
|
|
21
|
+
QHBoxLayout,
|
|
22
|
+
QSizePolicy,
|
|
23
|
+
QScrollArea,
|
|
24
|
+
QToolButton,
|
|
25
|
+
QPushButton,
|
|
26
|
+
QWidget,
|
|
27
|
+
QFrame,
|
|
28
|
+
QStyle,
|
|
29
|
+
QLabel,
|
|
30
|
+
)
|
|
15
31
|
|
|
16
32
|
from pygpt_net.ui.widget.dialog.base import BaseDialog
|
|
17
33
|
from pygpt_net.ui.widget.image.display import ImageLabel
|
|
@@ -37,8 +53,9 @@ class DialogSpawner:
|
|
|
37
53
|
:return: BaseDialog instance
|
|
38
54
|
"""
|
|
39
55
|
dialog = ImageViewerDialog(self.window, self.id)
|
|
40
|
-
dialog.disable_geometry_store =
|
|
56
|
+
dialog.disable_geometry_store = False
|
|
41
57
|
dialog.id = id
|
|
58
|
+
dialog.shared_id = self.id
|
|
42
59
|
|
|
43
60
|
source = ImageLabel(dialog, self.path)
|
|
44
61
|
source.setVisible(False)
|
|
@@ -55,7 +72,18 @@ class DialogSpawner:
|
|
|
55
72
|
row.addWidget(scroll)
|
|
56
73
|
|
|
57
74
|
layout = QVBoxLayout()
|
|
75
|
+
# remove extra outer margins to bring the toolbar closer to the top/menu bar
|
|
76
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
77
|
+
layout.setSpacing(0)
|
|
78
|
+
|
|
79
|
+
# full-width toolbar frame; icons cluster inside stays compact on the left
|
|
80
|
+
toolbar = dialog.setup_toolbar()
|
|
81
|
+
layout.addWidget(toolbar)
|
|
82
|
+
# image area
|
|
58
83
|
layout.addLayout(row)
|
|
84
|
+
# bottom status bar
|
|
85
|
+
statusbar = dialog.setup_statusbar()
|
|
86
|
+
layout.addWidget(statusbar)
|
|
59
87
|
|
|
60
88
|
dialog.append_layout(layout)
|
|
61
89
|
dialog.source = source
|
|
@@ -97,6 +125,12 @@ class ImageViewerDialog(BaseDialog):
|
|
|
97
125
|
self._icon_save = QIcon(":/icons/save.svg")
|
|
98
126
|
self._icon_logout = QIcon(":/icons/logout.svg")
|
|
99
127
|
|
|
128
|
+
# toolbar icons
|
|
129
|
+
self._icon_prev = QIcon(":/icons/back.svg")
|
|
130
|
+
self._icon_next = QIcon(":/icons/forward.svg")
|
|
131
|
+
self._icon_copy = QIcon(":/icons/copy.svg")
|
|
132
|
+
self._icon_fullscreen = QIcon(":/icons/fullscreen.svg")
|
|
133
|
+
|
|
100
134
|
# zoom / pan state
|
|
101
135
|
self._zoom_mode = 'fit' # 'fit' or 'manual'
|
|
102
136
|
self._zoom_factor = 1.0 # current manual factor (image space)
|
|
@@ -111,6 +145,21 @@ class ImageViewerDialog(BaseDialog):
|
|
|
111
145
|
self._max_widget_dim = 32768 # max single dimension (px) for the view widget
|
|
112
146
|
self._max_total_pixels = 80_000_000 # max total pixels of the view widget (about 80MP)
|
|
113
147
|
|
|
148
|
+
# buttons map to keep enabled state in sync with actions
|
|
149
|
+
self._tb_btns = {}
|
|
150
|
+
|
|
151
|
+
# status bar widgets and metadata
|
|
152
|
+
self.status_bar = None
|
|
153
|
+
self.lbl_index = None
|
|
154
|
+
self.lbl_resolution = None
|
|
155
|
+
self.lbl_zoom = None
|
|
156
|
+
self.lbl_extra = None
|
|
157
|
+
|
|
158
|
+
self._meta_index = 0 # 1-based index of file in dir
|
|
159
|
+
self._meta_total = 0 # total files in dir
|
|
160
|
+
self._meta_img_size = QSize() # original image size
|
|
161
|
+
self._meta_file_size = "" # formatted file size string
|
|
162
|
+
|
|
114
163
|
def append_layout(self, layout):
|
|
115
164
|
"""
|
|
116
165
|
Update layout
|
|
@@ -152,6 +201,8 @@ class ImageViewerDialog(BaseDialog):
|
|
|
152
201
|
self._fit_factor = self._compute_fit_factor(src.size(), target_size)
|
|
153
202
|
self._last_src_key = key
|
|
154
203
|
self._last_target_size = target_size
|
|
204
|
+
# update status bar to reflect new zoom and display size
|
|
205
|
+
self._refresh_statusbar()
|
|
155
206
|
super(ImageViewerDialog, self).resizeEvent(event)
|
|
156
207
|
|
|
157
208
|
def setup_menu(self) -> QMenuBar:
|
|
@@ -196,6 +247,196 @@ class ImageViewerDialog(BaseDialog):
|
|
|
196
247
|
|
|
197
248
|
return self.menu_bar
|
|
198
249
|
|
|
250
|
+
def setup_toolbar(self) -> QFrame:
|
|
251
|
+
"""
|
|
252
|
+
Setup top toolbar.
|
|
253
|
+
Full-width frame (Expanding), with a fixed-size icons strip on the left that does not stretch.
|
|
254
|
+
"""
|
|
255
|
+
bar = QFrame(self)
|
|
256
|
+
bar.setObjectName("image_viewer_toolbar")
|
|
257
|
+
bar.setFrameShape(QFrame.NoFrame)
|
|
258
|
+
bar.setMinimumHeight(35)
|
|
259
|
+
bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # full width, fixed height
|
|
260
|
+
|
|
261
|
+
# scoped minimal style for icon-only QPushButtons
|
|
262
|
+
bar.setStyleSheet(
|
|
263
|
+
"""
|
|
264
|
+
#image_viewer_toolbar {
|
|
265
|
+
border: none;
|
|
266
|
+
}
|
|
267
|
+
#image_viewer_toolbar QPushButton {
|
|
268
|
+
border: none;
|
|
269
|
+
padding: 0;
|
|
270
|
+
}
|
|
271
|
+
#image_viewer_toolbar QPushButton:disabled {
|
|
272
|
+
opacity: 0.5;
|
|
273
|
+
}
|
|
274
|
+
"""
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# main container layout that fills the width
|
|
278
|
+
lay = QHBoxLayout(bar)
|
|
279
|
+
lay.setContentsMargins(0, 0, 0, 0)
|
|
280
|
+
lay.setSpacing(0)
|
|
281
|
+
|
|
282
|
+
# a compact, non-expanding strip that holds the icons
|
|
283
|
+
strip = QWidget(bar)
|
|
284
|
+
strip.setObjectName("image_viewer_toolbar_strip")
|
|
285
|
+
strip.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
286
|
+
|
|
287
|
+
strip_lay = QHBoxLayout(strip)
|
|
288
|
+
strip_lay.setContentsMargins(6, 6, 6, 6)
|
|
289
|
+
strip_lay.setSpacing(6)
|
|
290
|
+
|
|
291
|
+
# DPI-aware icon and button sizes; explicit fixed size prevents vertical squashing
|
|
292
|
+
pm = self.style().pixelMetric(QStyle.PM_ToolBarIconSize)
|
|
293
|
+
if not isinstance(pm, int) or pm <= 0:
|
|
294
|
+
pm = self.style().pixelMetric(QStyle.PM_SmallIconSize)
|
|
295
|
+
if not isinstance(pm, int) or pm <= 0:
|
|
296
|
+
pm = 20
|
|
297
|
+
# reduce icon height by ~5px (and width to keep square) as requested
|
|
298
|
+
icon_px = max(16, pm - 5)
|
|
299
|
+
btn_px = max(29, icon_px + 10) # keep compact square buttons; min reduced by 5px
|
|
300
|
+
icon_size = QSize(icon_px, icon_px)
|
|
301
|
+
|
|
302
|
+
def bind_button_to_action(btn: QPushButton, action: QAction):
|
|
303
|
+
"""Keep button in sync with the given action."""
|
|
304
|
+
btn.setIcon(action.icon())
|
|
305
|
+
btn.setToolTip(action.toolTip())
|
|
306
|
+
btn.setEnabled(action.isEnabled())
|
|
307
|
+
action.changed.connect(lambda: (
|
|
308
|
+
btn.setIcon(action.icon()),
|
|
309
|
+
btn.setToolTip(action.toolTip()),
|
|
310
|
+
btn.setEnabled(action.isEnabled())
|
|
311
|
+
))
|
|
312
|
+
btn.clicked.connect(action.trigger)
|
|
313
|
+
|
|
314
|
+
def mk_btn(key: str, icon: QIcon, tooltip: str) -> QPushButton:
|
|
315
|
+
"""Create a fixed-size square button with icon-only look."""
|
|
316
|
+
action = QAction(icon, "", self)
|
|
317
|
+
action.setToolTip(tooltip)
|
|
318
|
+
self.actions[key] = action
|
|
319
|
+
|
|
320
|
+
btn = QPushButton(strip)
|
|
321
|
+
btn.setObjectName("ivtb")
|
|
322
|
+
btn.setCursor(Qt.PointingHandCursor)
|
|
323
|
+
btn.setFocusPolicy(Qt.NoFocus)
|
|
324
|
+
btn.setFlat(True)
|
|
325
|
+
btn.setText("")
|
|
326
|
+
btn.setIcon(icon)
|
|
327
|
+
btn.setIconSize(icon_size)
|
|
328
|
+
btn.setFixedSize(btn_px, btn_px)
|
|
329
|
+
|
|
330
|
+
bind_button_to_action(btn, action)
|
|
331
|
+
self._tb_btns[key] = btn
|
|
332
|
+
|
|
333
|
+
strip_lay.addWidget(btn)
|
|
334
|
+
return btn
|
|
335
|
+
|
|
336
|
+
def add_sep():
|
|
337
|
+
sep = QFrame(strip)
|
|
338
|
+
sep.setFrameShape(QFrame.VLine)
|
|
339
|
+
sep.setFrameShadow(QFrame.Sunken)
|
|
340
|
+
sep.setFixedHeight(btn_px - 4)
|
|
341
|
+
strip_lay.addWidget(sep)
|
|
342
|
+
|
|
343
|
+
# prev
|
|
344
|
+
mk_btn("tb_prev", self._icon_prev, "Previous image")
|
|
345
|
+
self.actions["tb_prev"].triggered.connect(
|
|
346
|
+
lambda checked=False: self.window.tools.get("viewer").prev_by_id(self.id)
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# next
|
|
350
|
+
mk_btn("tb_next", self._icon_next, "Next image")
|
|
351
|
+
self.actions["tb_next"].triggered.connect(
|
|
352
|
+
lambda checked=False: self.window.tools.get("viewer").next_by_id(self.id)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
add_sep()
|
|
356
|
+
|
|
357
|
+
# open in directory
|
|
358
|
+
mk_btn("tb_open_dir", self._icon_folder, "Open in directory")
|
|
359
|
+
self.actions["tb_open_dir"].triggered.connect(
|
|
360
|
+
lambda checked=False: self.window.tools.get("viewer").open_dir_by_id(self.id)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# save as...
|
|
364
|
+
mk_btn("tb_save_as", self._icon_save, "Save as...")
|
|
365
|
+
self.actions["tb_save_as"].triggered.connect(
|
|
366
|
+
lambda checked=False: self.window.tools.get("viewer").save_by_id(self.id)
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# copy
|
|
370
|
+
mk_btn("tb_copy", self._icon_copy, "Copy to clipboard")
|
|
371
|
+
self.actions["tb_copy"].triggered.connect(
|
|
372
|
+
lambda checked=False: self.window.tools.get("viewer").copy_by_id(self.id)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
add_sep()
|
|
376
|
+
|
|
377
|
+
# fullscreen
|
|
378
|
+
mk_btn("tb_fullscreen", self._icon_fullscreen, "Toggle fullscreen")
|
|
379
|
+
self.actions["tb_fullscreen"].triggered.connect(
|
|
380
|
+
lambda checked=False: self.window.tools.get("viewer").toggle_fullscreen_by_id(self.id)
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# initial enabled state; actual state adjusted on image load
|
|
384
|
+
for k in ("tb_prev", "tb_next", "tb_open_dir", "tb_save_as", "tb_copy", "tb_fullscreen"):
|
|
385
|
+
if k in self.actions:
|
|
386
|
+
self.actions[k].setEnabled(k == "tb_fullscreen")
|
|
387
|
+
|
|
388
|
+
# compose: icons strip on the left, stretch fills the rest so frame spans full width
|
|
389
|
+
lay.addWidget(strip)
|
|
390
|
+
lay.addStretch(1)
|
|
391
|
+
|
|
392
|
+
return bar
|
|
393
|
+
|
|
394
|
+
def setup_statusbar(self) -> QFrame:
|
|
395
|
+
"""
|
|
396
|
+
Setup bottom status bar with four columns:
|
|
397
|
+
1) file index in directory (e.g., 1/13)
|
|
398
|
+
2) displayed size (e.g., 1280x720 px)
|
|
399
|
+
3) current zoom in percent
|
|
400
|
+
4) original image resolution and file size on the right
|
|
401
|
+
"""
|
|
402
|
+
bar = QFrame(self)
|
|
403
|
+
bar.setObjectName("image_viewer_statusbar")
|
|
404
|
+
bar.setFrameShape(QFrame.NoFrame)
|
|
405
|
+
bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
406
|
+
bar.setStyleSheet("""
|
|
407
|
+
#image_viewer_statusbar {
|
|
408
|
+
border: none;
|
|
409
|
+
}
|
|
410
|
+
#image_viewer_statusbar QLabel {
|
|
411
|
+
color: #686868;
|
|
412
|
+
font-size: 12px;
|
|
413
|
+
padding: 4px 8px;
|
|
414
|
+
}
|
|
415
|
+
""")
|
|
416
|
+
|
|
417
|
+
lay = QHBoxLayout(bar)
|
|
418
|
+
lay.setContentsMargins(0, 0, 0, 0)
|
|
419
|
+
lay.setSpacing(0)
|
|
420
|
+
|
|
421
|
+
self.lbl_index = QLabel("-", bar)
|
|
422
|
+
self.lbl_resolution = QLabel("-", bar)
|
|
423
|
+
self.lbl_zoom = QLabel("-", bar)
|
|
424
|
+
self.lbl_extra = QLabel("-", bar)
|
|
425
|
+
|
|
426
|
+
# make columns distribute evenly; order updated to show current (displayed) on the left,
|
|
427
|
+
# and original with file size on the right: index | displayed | zoom | original (+ size)
|
|
428
|
+
for w in (self.lbl_index, self.lbl_extra, self.lbl_zoom, self.lbl_resolution):
|
|
429
|
+
w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
430
|
+
|
|
431
|
+
lay.addWidget(self.lbl_index, 1, Qt.AlignLeft)
|
|
432
|
+
lay.addWidget(self.lbl_extra, 1, Qt.AlignLeft)
|
|
433
|
+
lay.addWidget(self.lbl_zoom, 1, Qt.AlignCenter)
|
|
434
|
+
lay.addWidget(self.lbl_resolution, 1, Qt.AlignRight)
|
|
435
|
+
|
|
436
|
+
self.status_bar = bar
|
|
437
|
+
self._refresh_statusbar()
|
|
438
|
+
return bar
|
|
439
|
+
|
|
199
440
|
# =========================
|
|
200
441
|
# Zoom / pan implementation
|
|
201
442
|
# =========================
|
|
@@ -265,10 +506,8 @@ class ImageViewerDialog(BaseDialog):
|
|
|
265
506
|
)
|
|
266
507
|
self._zoom_factor = self._fit_factor
|
|
267
508
|
self._zoom_mode = 'manual'
|
|
268
|
-
# switch to manual rendering path: original pixmap + scaled contents
|
|
269
509
|
self.scroll_area.setWidgetResizable(False)
|
|
270
510
|
self.pixmap.setScaledContents(True)
|
|
271
|
-
# ensure we display original image for better performance (no giant intermediate pixmaps)
|
|
272
511
|
self.pixmap.setPixmap(self.source.pixmap())
|
|
273
512
|
|
|
274
513
|
old_w = max(1, self.pixmap.width())
|
|
@@ -286,10 +525,8 @@ class ImageViewerDialog(BaseDialog):
|
|
|
286
525
|
return False
|
|
287
526
|
|
|
288
527
|
step = self._zoom_step if angle > 0 else (1.0 / self._zoom_step)
|
|
289
|
-
# compute tentative new factor and clamp by hard min/max and size guards
|
|
290
528
|
tentative = self._zoom_factor * step
|
|
291
529
|
tentative = max(self._min_zoom, min(self._max_zoom, tentative))
|
|
292
|
-
# apply size-based guards to avoid extremely huge widget sizes
|
|
293
530
|
tentative = self._clamp_factor_by_size(tentative)
|
|
294
531
|
|
|
295
532
|
if abs(tentative - self._zoom_factor) < 1e-9:
|
|
@@ -300,7 +537,6 @@ class ImageViewerDialog(BaseDialog):
|
|
|
300
537
|
hbar = self.scroll_area.horizontalScrollBar()
|
|
301
538
|
vbar = self.scroll_area.verticalScrollBar()
|
|
302
539
|
|
|
303
|
-
# position in content coords before zoom (keep point under cursor stable)
|
|
304
540
|
content_x = hbar.value() + vp_pos.x()
|
|
305
541
|
content_y = vbar.value() + vp_pos.y()
|
|
306
542
|
rx = content_x / float(old_w)
|
|
@@ -364,23 +600,19 @@ class ImageViewerDialog(BaseDialog):
|
|
|
364
600
|
iw = max(1, src.width())
|
|
365
601
|
ih = max(1, src.height())
|
|
366
602
|
|
|
367
|
-
# only guard when zooming in; zooming out should not be limited by these caps
|
|
368
603
|
if factor <= 1.0:
|
|
369
604
|
return factor
|
|
370
605
|
|
|
371
|
-
# desired size
|
|
372
606
|
dw = iw * factor
|
|
373
607
|
dh = ih * factor
|
|
374
608
|
|
|
375
609
|
scale = 1.0
|
|
376
610
|
|
|
377
|
-
# total pixel cap
|
|
378
611
|
total = dw * dh
|
|
379
612
|
if total > self._max_total_pixels:
|
|
380
613
|
from math import sqrt
|
|
381
614
|
scale = min(scale, sqrt(self._max_total_pixels / float(total)))
|
|
382
615
|
|
|
383
|
-
# dimension caps
|
|
384
616
|
if dw * scale > self._max_widget_dim:
|
|
385
617
|
scale = min(scale, self._max_widget_dim / float(dw))
|
|
386
618
|
if dh * scale > self._max_widget_dim:
|
|
@@ -393,10 +625,6 @@ class ImageViewerDialog(BaseDialog):
|
|
|
393
625
|
def _set_scaled_pixmap_by_factor(self, factor: float):
|
|
394
626
|
"""
|
|
395
627
|
Scale and display the image using provided factor relative to the original image.
|
|
396
|
-
In manual mode this avoids allocating giant intermediate QPixmaps by:
|
|
397
|
-
- drawing the original pixmap;
|
|
398
|
-
- enabling scaled contents;
|
|
399
|
-
- resizing the label to the required size.
|
|
400
628
|
"""
|
|
401
629
|
if not self._has_image():
|
|
402
630
|
return
|
|
@@ -405,31 +633,30 @@ class ImageViewerDialog(BaseDialog):
|
|
|
405
633
|
iw = max(1, src.width())
|
|
406
634
|
ih = max(1, src.height())
|
|
407
635
|
|
|
408
|
-
# target size based on factor (KeepAspectRatio preserved by proportional math)
|
|
409
636
|
new_w = max(1, int(round(iw * factor)))
|
|
410
637
|
new_h = max(1, int(round(ih * factor)))
|
|
411
638
|
|
|
412
|
-
# enforce guards once more to be safe
|
|
413
639
|
guarded_factor = self._clamp_factor_by_size(factor)
|
|
414
640
|
if abs(guarded_factor - factor) > 1e-9:
|
|
415
641
|
new_w = max(1, int(round(iw * guarded_factor)))
|
|
416
642
|
new_h = max(1, int(round(ih * guarded_factor)))
|
|
417
|
-
self._zoom_factor = guarded_factor
|
|
643
|
+
self._zoom_factor = guarded_factor
|
|
418
644
|
|
|
419
645
|
if self._zoom_mode == 'manual':
|
|
420
|
-
# ensure manual path uses original pixmap and scaled contents
|
|
421
646
|
if self.pixmap.pixmap() is None or self.pixmap.pixmap().cacheKey() != src.cacheKey():
|
|
422
647
|
self.pixmap.setPixmap(src)
|
|
423
648
|
self.pixmap.setScaledContents(True)
|
|
424
649
|
self.pixmap.resize(new_w, new_h)
|
|
425
650
|
else:
|
|
426
|
-
# fallback (not expected here): keep classic high-quality scaling
|
|
427
651
|
scaled = src.scaled(new_w, new_h, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
428
652
|
self.pixmap.setScaledContents(False)
|
|
429
653
|
self.pixmap.setPixmap(scaled)
|
|
430
654
|
if self.scroll_area is not None:
|
|
431
655
|
self.scroll_area.setWidgetResizable(True)
|
|
432
656
|
|
|
657
|
+
# keep status bar in sync with zoom and current display size
|
|
658
|
+
self._refresh_statusbar()
|
|
659
|
+
|
|
433
660
|
def _event_pos(self, event) -> QPoint:
|
|
434
661
|
"""
|
|
435
662
|
Extract integer QPoint from mouse/touchpad event position (supports QPointF in 6.9+).
|
|
@@ -438,4 +665,74 @@ class ImageViewerDialog(BaseDialog):
|
|
|
438
665
|
return event.position().toPoint()
|
|
439
666
|
if hasattr(event, "pos"):
|
|
440
667
|
return event.pos()
|
|
441
|
-
return QPoint(0, 0)
|
|
668
|
+
return QPoint(0, 0)
|
|
669
|
+
|
|
670
|
+
# =========================
|
|
671
|
+
# Status bar helpers
|
|
672
|
+
# =========================
|
|
673
|
+
|
|
674
|
+
def set_status_meta(self, index: int = None, total: int = None, img_size: QSize = None, file_size_str: str = None):
|
|
675
|
+
"""
|
|
676
|
+
Update persistent metadata shown in status bar (index/total, original image size, file size).
|
|
677
|
+
"""
|
|
678
|
+
if index is not None:
|
|
679
|
+
self._meta_index = max(0, int(index))
|
|
680
|
+
if total is not None:
|
|
681
|
+
self._meta_total = max(0, int(total))
|
|
682
|
+
if img_size is not None:
|
|
683
|
+
self._meta_img_size = QSize(max(0, img_size.width()), max(0, img_size.height()))
|
|
684
|
+
if file_size_str is not None:
|
|
685
|
+
self._meta_file_size = file_size_str or ""
|
|
686
|
+
self._refresh_statusbar()
|
|
687
|
+
|
|
688
|
+
def _current_zoom_percent(self) -> int:
|
|
689
|
+
"""
|
|
690
|
+
Return current zoom in percent, based on mode.
|
|
691
|
+
"""
|
|
692
|
+
factor = self._fit_factor if self._zoom_mode == 'fit' else self._zoom_factor
|
|
693
|
+
try:
|
|
694
|
+
return max(1, int(round(factor * 100.0)))
|
|
695
|
+
except Exception:
|
|
696
|
+
return 100
|
|
697
|
+
|
|
698
|
+
def _displayed_size(self) -> QSize:
|
|
699
|
+
"""
|
|
700
|
+
Return the currently displayed image size in pixels.
|
|
701
|
+
"""
|
|
702
|
+
if self._zoom_mode == 'manual' and self.pixmap is not None:
|
|
703
|
+
return QSize(max(0, self.pixmap.width()), max(0, self.pixmap.height()))
|
|
704
|
+
# in fit mode or when using a scaled pixmap
|
|
705
|
+
if self.pixmap is not None and self.pixmap.pixmap() is not None:
|
|
706
|
+
pm = self.pixmap.pixmap()
|
|
707
|
+
return QSize(max(0, pm.width()), max(0, pm.height()))
|
|
708
|
+
return QSize(0, 0)
|
|
709
|
+
|
|
710
|
+
def _refresh_statusbar(self):
|
|
711
|
+
"""
|
|
712
|
+
Refresh all status bar labels from current metadata and state.
|
|
713
|
+
"""
|
|
714
|
+
if self.lbl_index is None:
|
|
715
|
+
return
|
|
716
|
+
|
|
717
|
+
# 1) index/total
|
|
718
|
+
if self._meta_index > 0 and self._meta_total > 0:
|
|
719
|
+
self.lbl_index.setText(f"{self._meta_index}/{self._meta_total}")
|
|
720
|
+
else:
|
|
721
|
+
self.lbl_index.setText("-")
|
|
722
|
+
|
|
723
|
+
# 2) left: displayed size only
|
|
724
|
+
dsz = self._displayed_size()
|
|
725
|
+
dsz_txt = f"{dsz.width()}x{dsz.height()} px" if dsz.width() > 0 and dsz.height() > 0 else "-"
|
|
726
|
+
self.lbl_extra.setText(dsz_txt)
|
|
727
|
+
|
|
728
|
+
# 3) zoom percent (center)
|
|
729
|
+
self.lbl_zoom.setText(f"{self._current_zoom_percent()}%")
|
|
730
|
+
|
|
731
|
+
# 4) right: original resolution with file size appended
|
|
732
|
+
if self._meta_img_size.width() > 0 and self._meta_img_size.height() > 0:
|
|
733
|
+
right_txt = f"{self._meta_img_size.width()}x{self._meta_img_size.height()} px"
|
|
734
|
+
if self._meta_file_size:
|
|
735
|
+
right_txt += f" | {self._meta_file_size}"
|
|
736
|
+
self.lbl_resolution.setText(right_txt)
|
|
737
|
+
else:
|
|
738
|
+
self.lbl_resolution.setText("-")
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2026.01.05 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout
|
|
@@ -63,8 +63,9 @@ class DialogSpawner:
|
|
|
63
63
|
layout.addLayout(bottom_layout)
|
|
64
64
|
|
|
65
65
|
dialog = EditorFileDialog(self.window)
|
|
66
|
-
dialog.disable_geometry_store =
|
|
66
|
+
dialog.disable_geometry_store = False
|
|
67
67
|
dialog.id = id
|
|
68
|
+
dialog.shared_id = "text-edit"
|
|
68
69
|
dialog.append_layout(layout)
|
|
69
70
|
dialog.setWindowTitle("Text editor")
|
|
70
71
|
|
|
File without changes
|
pygpt_net/ui/dialog/assistant.py
CHANGED
|
@@ -46,7 +46,7 @@ class Assistant(BaseConfigDialog):
|
|
|
46
46
|
self.window.ui.nodes['assistant.btn.store.editor'] = QPushButton(QIcon(":/icons/db.svg"), "")
|
|
47
47
|
self.window.ui.nodes['assistant.btn.store.editor'].setToolTip(trans('dialog.remote_store.openai'))
|
|
48
48
|
self.window.ui.nodes['assistant.btn.store.editor'].clicked.connect(
|
|
49
|
-
lambda: self.window.controller.remote_store.
|
|
49
|
+
lambda: self.window.controller.remote_store.toggle_editor(provider="openai")
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
self.window.ui.nodes['assistant.btn.store.editor'].setAutoDefault(False)
|
pygpt_net/ui/dialog/plugins.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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.06 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt
|
|
@@ -176,8 +176,12 @@ class Plugins:
|
|
|
176
176
|
desc_txt = plugin.description
|
|
177
177
|
if plugin.use_locale:
|
|
178
178
|
domain = f"plugin.{plugin.id}"
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
translated_name = trans('plugin.name', False, domain)
|
|
180
|
+
translated_desc = trans('plugin.description', False, domain)
|
|
181
|
+
if translated_name != 'plugin.name':
|
|
182
|
+
name_txt = translated_name
|
|
183
|
+
if translated_desc != 'plugin.description':
|
|
184
|
+
desc_txt = translated_desc
|
|
181
185
|
|
|
182
186
|
self.window.ui.nodes[desc_key] = DescLabel(desc_txt)
|
|
183
187
|
self.window.ui.nodes[desc_key].setAlignment(Qt.AlignCenter)
|
|
@@ -423,8 +427,12 @@ class Plugins:
|
|
|
423
427
|
# translate if localization is enabled
|
|
424
428
|
if plugin.use_locale and allow_locale:
|
|
425
429
|
domain = f"plugin.{plugin.id}"
|
|
426
|
-
|
|
427
|
-
|
|
430
|
+
translated_label = trans(f"{key}.label", False, domain)
|
|
431
|
+
translated_description = trans(f"{key}.description", False, domain)
|
|
432
|
+
if translated_label != f"{key}.label":
|
|
433
|
+
txt_title = translated_label
|
|
434
|
+
if translated_description != f"{key}.description":
|
|
435
|
+
txt_desc = translated_description
|
|
428
436
|
# txt_tooltip = trans(f"{key}.tooltip", False, domain)
|
|
429
437
|
|
|
430
438
|
# if empty tooltip then use description
|