pygpt-net 2.6.67__py3-none-any.whl → 2.7.1__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 +20 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/assistant/assistant.py +13 -8
- pygpt_net/controller/assistant/batch.py +29 -15
- pygpt_net/controller/assistant/files.py +19 -14
- pygpt_net/controller/assistant/store.py +63 -41
- pygpt_net/controller/attachment/attachment.py +45 -35
- pygpt_net/controller/chat/attachment.py +50 -39
- pygpt_net/controller/config/field/dictionary.py +26 -14
- pygpt_net/controller/ctx/common.py +27 -17
- pygpt_net/controller/ctx/ctx.py +185 -101
- pygpt_net/controller/files/files.py +101 -41
- pygpt_net/controller/idx/indexer.py +87 -31
- pygpt_net/controller/kernel/kernel.py +13 -2
- pygpt_net/controller/mode/mode.py +3 -3
- pygpt_net/controller/model/editor.py +70 -15
- pygpt_net/controller/model/importer.py +153 -54
- pygpt_net/controller/painter/common.py +43 -11
- pygpt_net/controller/painter/painter.py +2 -2
- pygpt_net/controller/presets/experts.py +68 -15
- pygpt_net/controller/presets/presets.py +72 -36
- pygpt_net/controller/settings/profile.py +76 -35
- pygpt_net/controller/settings/workdir.py +70 -39
- pygpt_net/core/assistants/files.py +20 -18
- pygpt_net/core/filesystem/actions.py +111 -10
- pygpt_net/core/filesystem/filesystem.py +72 -1
- pygpt_net/core/filesystem/packer.py +161 -1
- pygpt_net/core/idx/idx.py +12 -11
- pygpt_net/core/idx/worker.py +13 -1
- pygpt_net/core/image/image.py +2 -2
- pygpt_net/core/models/models.py +4 -4
- pygpt_net/core/profile/profile.py +13 -3
- pygpt_net/core/video/video.py +2 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/style.dark.css +45 -0
- pygpt_net/data/css/style.light.css +46 -0
- pygpt_net/data/locale/locale.de.ini +5 -1
- pygpt_net/data/locale/locale.en.ini +5 -1
- pygpt_net/data/locale/locale.es.ini +5 -1
- pygpt_net/data/locale/locale.fr.ini +5 -1
- pygpt_net/data/locale/locale.it.ini +5 -1
- pygpt_net/data/locale/locale.pl.ini +6 -2
- pygpt_net/data/locale/locale.uk.ini +5 -1
- pygpt_net/data/locale/locale.zh.ini +5 -1
- pygpt_net/provider/api/openai/__init__.py +4 -2
- pygpt_net/provider/core/config/patch.py +17 -1
- pygpt_net/tools/image_viewer/tool.py +17 -0
- pygpt_net/tools/text_editor/tool.py +9 -0
- pygpt_net/ui/__init__.py +2 -2
- pygpt_net/ui/dialog/preset.py +1 -0
- pygpt_net/ui/layout/ctx/ctx_list.py +16 -6
- pygpt_net/ui/layout/toolbox/image.py +2 -1
- pygpt_net/ui/layout/toolbox/indexes.py +2 -0
- pygpt_net/ui/layout/toolbox/video.py +5 -1
- pygpt_net/ui/main.py +3 -1
- pygpt_net/ui/widget/calendar/select.py +3 -3
- pygpt_net/ui/widget/draw/painter.py +238 -51
- pygpt_net/ui/widget/filesystem/explorer.py +1164 -142
- pygpt_net/ui/widget/lists/assistant.py +185 -24
- pygpt_net/ui/widget/lists/assistant_store.py +245 -42
- pygpt_net/ui/widget/lists/attachment.py +230 -47
- pygpt_net/ui/widget/lists/attachment_ctx.py +189 -33
- pygpt_net/ui/widget/lists/base_list_combo.py +2 -2
- pygpt_net/ui/widget/lists/context.py +1253 -70
- pygpt_net/ui/widget/lists/experts.py +110 -8
- pygpt_net/ui/widget/lists/model_editor.py +217 -14
- pygpt_net/ui/widget/lists/model_importer.py +125 -6
- pygpt_net/ui/widget/lists/preset.py +460 -71
- pygpt_net/ui/widget/lists/profile.py +149 -27
- pygpt_net/ui/widget/lists/uploaded.py +230 -38
- pygpt_net/ui/widget/option/combo.py +1211 -33
- pygpt_net/ui/widget/option/dictionary.py +35 -7
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/METADATA +22 -57
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/RECORD +78 -78
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/entry_points.txt +0 -0
|
@@ -6,11 +6,11 @@
|
|
|
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.27 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
|
-
from typing import List
|
|
13
|
+
from typing import List, Union
|
|
14
14
|
|
|
15
15
|
from PySide6.QtGui import QAction, QIcon
|
|
16
16
|
from PySide6.QtWidgets import QWidget
|
|
@@ -27,16 +27,22 @@ class Actions:
|
|
|
27
27
|
"""
|
|
28
28
|
self.window = window
|
|
29
29
|
|
|
30
|
-
def has_preview(self, path: str) -> bool:
|
|
30
|
+
def has_preview(self, path: Union[str, list]) -> bool:
|
|
31
31
|
"""
|
|
32
32
|
Check if file has preview action
|
|
33
33
|
|
|
34
|
-
:param path: path to file
|
|
34
|
+
:param path: path to file or list of paths
|
|
35
35
|
:return: True if file has preview
|
|
36
36
|
"""
|
|
37
|
-
if
|
|
37
|
+
if isinstance(path, list):
|
|
38
|
+
for p in path:
|
|
39
|
+
if not os.path.isdir(p):
|
|
40
|
+
return True # allow preview for any file in the list
|
|
38
41
|
return False
|
|
39
|
-
|
|
42
|
+
else:
|
|
43
|
+
if os.path.isdir(path):
|
|
44
|
+
return False
|
|
45
|
+
return True
|
|
40
46
|
|
|
41
47
|
def has_use(self, path: str) -> bool:
|
|
42
48
|
"""
|
|
@@ -48,14 +54,93 @@ class Actions:
|
|
|
48
54
|
return (self.window.core.filesystem.types.is_image(path)
|
|
49
55
|
or self.window.core.filesystem.types.is_video(path))
|
|
50
56
|
|
|
51
|
-
def
|
|
57
|
+
def get_preview_batch(self, parent: QWidget, path: list) -> List[QAction]:
|
|
58
|
+
"""
|
|
59
|
+
Get preview actions for multiple files
|
|
60
|
+
|
|
61
|
+
:param parent: explorer widget
|
|
62
|
+
:param path: list of paths
|
|
63
|
+
:return: list of context menu actions
|
|
64
|
+
"""
|
|
65
|
+
actions = []
|
|
66
|
+
paths_video = []
|
|
67
|
+
paths_image = []
|
|
68
|
+
paths_edit = []
|
|
69
|
+
for p in path:
|
|
70
|
+
if os.path.isdir(p):
|
|
71
|
+
continue
|
|
72
|
+
actions = []
|
|
73
|
+
if (self.window.core.filesystem.types.is_video(p)
|
|
74
|
+
or self.window.core.filesystem.types.is_audio(p)):
|
|
75
|
+
paths_video.append(p)
|
|
76
|
+
elif self.window.core.filesystem.types.is_image(p):
|
|
77
|
+
paths_image.append(p)
|
|
78
|
+
else:
|
|
79
|
+
extra_excluded = ["pdf", "docx", "doc", "xlsx", "xls", "pptx", "ppt"]
|
|
80
|
+
ext = os.path.splitext(p)[1][1:].lower()
|
|
81
|
+
if ext not in self.window.core.filesystem.types.get_excluded_extensions() + extra_excluded:
|
|
82
|
+
paths_edit.append(p)
|
|
83
|
+
|
|
84
|
+
# video/audio - single action for first file
|
|
85
|
+
if paths_video:
|
|
86
|
+
p = paths_video[0] # single action for first video
|
|
87
|
+
action = QAction(
|
|
88
|
+
QIcon(":/icons/video.svg"),
|
|
89
|
+
trans('action.video.play'),
|
|
90
|
+
parent,
|
|
91
|
+
)
|
|
92
|
+
action.triggered.connect(
|
|
93
|
+
lambda: self.window.tools.get("player").play(p),
|
|
94
|
+
)
|
|
95
|
+
actions.append(action)
|
|
96
|
+
action = QAction(
|
|
97
|
+
QIcon(":/icons/hearing.svg"),
|
|
98
|
+
trans('action.video.transcribe'),
|
|
99
|
+
parent,
|
|
100
|
+
)
|
|
101
|
+
action.triggered.connect(
|
|
102
|
+
lambda: self.window.tools.get("transcriber").from_file(p),
|
|
103
|
+
)
|
|
104
|
+
actions.append(action)
|
|
105
|
+
|
|
106
|
+
# image - batch preview
|
|
107
|
+
if paths_image:
|
|
108
|
+
action = QAction(
|
|
109
|
+
QIcon(":/icons/image.svg"),
|
|
110
|
+
trans('action.preview'),
|
|
111
|
+
parent,
|
|
112
|
+
)
|
|
113
|
+
action.triggered.connect(
|
|
114
|
+
lambda: self.window.tools.get("viewer").open_preview_batch(paths_image),
|
|
115
|
+
)
|
|
116
|
+
actions.append(action)
|
|
117
|
+
|
|
118
|
+
# edit - batch edit
|
|
119
|
+
if paths_edit:
|
|
120
|
+
action = QAction(
|
|
121
|
+
QIcon(":/icons/edit.svg"),
|
|
122
|
+
trans('action.edit'),
|
|
123
|
+
parent,
|
|
124
|
+
)
|
|
125
|
+
action.triggered.connect(
|
|
126
|
+
lambda: self.window.tools.get("editor").open_batch(paths_edit),
|
|
127
|
+
)
|
|
128
|
+
actions.append(action)
|
|
129
|
+
|
|
130
|
+
return actions
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_preview(self, parent: QWidget, path: Union[str, list]) -> List[QAction]:
|
|
52
134
|
"""
|
|
53
135
|
Get preview actions for context menu
|
|
54
136
|
|
|
55
137
|
:param parent: explorer widget
|
|
56
|
-
:param path: path to file
|
|
138
|
+
:param path: path to file or list of paths
|
|
57
139
|
:return: list of context menu actions
|
|
58
140
|
"""
|
|
141
|
+
if isinstance(path, list):
|
|
142
|
+
return self.get_preview_batch(parent, path)
|
|
143
|
+
|
|
59
144
|
actions = []
|
|
60
145
|
if (self.window.core.filesystem.types.is_video(path)
|
|
61
146
|
or self.window.core.filesystem.types.is_audio(path)):
|
|
@@ -102,14 +187,30 @@ class Actions:
|
|
|
102
187
|
actions.append(action)
|
|
103
188
|
return actions
|
|
104
189
|
|
|
105
|
-
def
|
|
190
|
+
def get_use_batch(self, parent: QWidget, path: list) -> List[QAction]:
|
|
191
|
+
"""
|
|
192
|
+
Get use actions for multiple files
|
|
193
|
+
|
|
194
|
+
:param parent: explorer widget
|
|
195
|
+
:param path: list of paths
|
|
196
|
+
:return: list of context menu actions
|
|
197
|
+
"""
|
|
198
|
+
actions = []
|
|
199
|
+
for p in path:
|
|
200
|
+
actions.extend(self.get_use(parent, p))
|
|
201
|
+
return actions
|
|
202
|
+
|
|
203
|
+
def get_use(self, parent: QWidget, path: Union[str, list]) -> List[QAction]:
|
|
106
204
|
"""
|
|
107
205
|
Get use actions for context menu
|
|
108
206
|
|
|
109
207
|
:param parent: explorer widget
|
|
110
|
-
:param path: path to file
|
|
208
|
+
:param path: path to file or list of paths
|
|
111
209
|
:return: list of context menu actions
|
|
112
210
|
"""
|
|
211
|
+
if isinstance(path, list):
|
|
212
|
+
return self.get_use_batch(parent, path)
|
|
213
|
+
|
|
113
214
|
actions = []
|
|
114
215
|
if self.window.core.filesystem.types.is_image(path):
|
|
115
216
|
action = QAction(
|
|
@@ -429,7 +429,8 @@ class Filesystem:
|
|
|
429
429
|
os.remove(item_path)
|
|
430
430
|
else:
|
|
431
431
|
if item not in excluded_dirs:
|
|
432
|
-
|
|
432
|
+
if os.path.exists(item_path) and os.path.isdir(item_path):
|
|
433
|
+
shutil.rmtree(item_path)
|
|
433
434
|
|
|
434
435
|
return True
|
|
435
436
|
|
|
@@ -479,3 +480,73 @@ class Filesystem:
|
|
|
479
480
|
else:
|
|
480
481
|
files = [os.path.join(path, f) for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
|
481
482
|
return files
|
|
483
|
+
|
|
484
|
+
# ===== Helpers for pack/unpack =====
|
|
485
|
+
|
|
486
|
+
def common_parent_dir(self, paths: List[str]) -> str:
|
|
487
|
+
"""
|
|
488
|
+
Return a sensible common parent directory for the given paths.
|
|
489
|
+
For a single directory selection returns that directory; for a single file returns its parent.
|
|
490
|
+
For multiple selections returns a common existing parent, if resolvable, otherwise the parent of the first path.
|
|
491
|
+
"""
|
|
492
|
+
if not paths:
|
|
493
|
+
return self.window.core.config.get_user_path()
|
|
494
|
+
norm = []
|
|
495
|
+
for p in paths:
|
|
496
|
+
p = os.path.abspath(p)
|
|
497
|
+
norm.append(p if os.path.isdir(p) else os.path.dirname(p))
|
|
498
|
+
if len(norm) == 1:
|
|
499
|
+
return norm[0]
|
|
500
|
+
try:
|
|
501
|
+
cp = os.path.commonpath(norm)
|
|
502
|
+
if os.path.isdir(cp):
|
|
503
|
+
return cp
|
|
504
|
+
return os.path.dirname(cp)
|
|
505
|
+
except Exception:
|
|
506
|
+
return norm[0]
|
|
507
|
+
|
|
508
|
+
def unique_path(self, directory: str, base_name: str, ext: str) -> str:
|
|
509
|
+
"""
|
|
510
|
+
Return a unique file path within 'directory' for 'base_name' and 'ext' (ext should include dot or be empty).
|
|
511
|
+
Uses 'name', 'name (1)', 'name (2)', ... scheme.
|
|
512
|
+
"""
|
|
513
|
+
os.makedirs(directory, exist_ok=True)
|
|
514
|
+
candidate = os.path.join(directory, f"{base_name}{ext}")
|
|
515
|
+
if not os.path.exists(candidate):
|
|
516
|
+
return candidate
|
|
517
|
+
i = 1
|
|
518
|
+
while True:
|
|
519
|
+
cand = os.path.join(directory, f"{base_name} ({i}){ext}")
|
|
520
|
+
if not os.path.exists(cand):
|
|
521
|
+
return cand
|
|
522
|
+
i += 1
|
|
523
|
+
|
|
524
|
+
def unique_dir(self, directory: str, base_name: str) -> str:
|
|
525
|
+
"""
|
|
526
|
+
Return a unique directory path 'directory/base_name', adding ' (n)' suffix when needed.
|
|
527
|
+
"""
|
|
528
|
+
os.makedirs(directory, exist_ok=True)
|
|
529
|
+
candidate = os.path.join(directory, base_name)
|
|
530
|
+
if not os.path.exists(candidate):
|
|
531
|
+
return candidate
|
|
532
|
+
i = 1
|
|
533
|
+
while True:
|
|
534
|
+
cand = os.path.join(directory, f"{base_name} ({i})")
|
|
535
|
+
if not os.path.exists(cand):
|
|
536
|
+
return cand
|
|
537
|
+
i += 1
|
|
538
|
+
|
|
539
|
+
def strip_archive_name(self, filename: str) -> str:
|
|
540
|
+
"""
|
|
541
|
+
Strip known archive extensions (.zip, .tar, .tar.gz, .tar.bz2, .tar.xz, .tgz, .tbz2, .txz) from a filename.
|
|
542
|
+
Returns filename without extension(s).
|
|
543
|
+
"""
|
|
544
|
+
combos = ['.tar.gz', '.tar.bz2', '.tar.xz', '.tgz', '.tbz2', '.txz']
|
|
545
|
+
lower = filename.lower()
|
|
546
|
+
for suf in combos:
|
|
547
|
+
if lower.endswith(suf):
|
|
548
|
+
return filename[:-len(suf)]
|
|
549
|
+
root, ext = os.path.splitext(filename)
|
|
550
|
+
if ext.lower() in ('.zip', '.tar'):
|
|
551
|
+
return root
|
|
552
|
+
return root
|
|
@@ -84,4 +84,164 @@ class Packer:
|
|
|
84
84
|
:param path: path to directory
|
|
85
85
|
"""
|
|
86
86
|
if os.path.exists(path) and os.path.isdir(path):
|
|
87
|
-
shutil.rmtree(path)
|
|
87
|
+
shutil.rmtree(path)
|
|
88
|
+
|
|
89
|
+
# ===== New high-level pack/unpack API (non-breaking) =====
|
|
90
|
+
|
|
91
|
+
def can_unpack(self, path: str) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Check using stdlib detectors whether given file is a supported archive.
|
|
94
|
+
"""
|
|
95
|
+
if not (path and os.path.isfile(path)):
|
|
96
|
+
return False
|
|
97
|
+
try:
|
|
98
|
+
import zipfile, tarfile
|
|
99
|
+
return zipfile.is_zipfile(path) or tarfile.is_tarfile(path)
|
|
100
|
+
except Exception:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def _detect_kind(self, path: str) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Detect archive kind: 'zip' or 'tar'. Returns '' if unknown.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
import zipfile, tarfile
|
|
109
|
+
if zipfile.is_zipfile(path):
|
|
110
|
+
return 'zip'
|
|
111
|
+
if tarfile.is_tarfile(path):
|
|
112
|
+
return 'tar'
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
return ''
|
|
116
|
+
|
|
117
|
+
def pack_paths(self, paths: list, fmt: str, dest_dir: str = None, base_name: str = None) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Pack given paths into a single archive.
|
|
120
|
+
|
|
121
|
+
:param paths: list of files/dirs to include
|
|
122
|
+
:param fmt: 'zip' or 'tar.gz'
|
|
123
|
+
:param dest_dir: output directory (default: common parent of paths)
|
|
124
|
+
:param base_name: output archive base name without extension (optional)
|
|
125
|
+
:return: created archive path or empty string on error
|
|
126
|
+
"""
|
|
127
|
+
if not paths:
|
|
128
|
+
return ""
|
|
129
|
+
fs = self.window.core.filesystem
|
|
130
|
+
paths = [os.path.abspath(p) for p in paths if p]
|
|
131
|
+
dest_dir = dest_dir or fs.common_parent_dir(paths)
|
|
132
|
+
|
|
133
|
+
if base_name is None:
|
|
134
|
+
if len(paths) == 1:
|
|
135
|
+
name = os.path.basename(paths[0].rstrip(os.sep))
|
|
136
|
+
if os.path.isfile(paths[0]):
|
|
137
|
+
root, _ = os.path.splitext(name)
|
|
138
|
+
base_name = root or name
|
|
139
|
+
else:
|
|
140
|
+
base_name = name
|
|
141
|
+
else:
|
|
142
|
+
base_name = "archive"
|
|
143
|
+
|
|
144
|
+
fmt = (fmt or "").lower()
|
|
145
|
+
if fmt == 'zip':
|
|
146
|
+
ext = ".zip"
|
|
147
|
+
out_path = fs.unique_path(dest_dir, base_name, ext)
|
|
148
|
+
try:
|
|
149
|
+
self._pack_zip(paths, out_path)
|
|
150
|
+
return out_path
|
|
151
|
+
except Exception as e:
|
|
152
|
+
try:
|
|
153
|
+
self.window.core.debug.log(e)
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
return ""
|
|
157
|
+
elif fmt in ('tar.gz', 'tgz'):
|
|
158
|
+
ext = ".tar.gz"
|
|
159
|
+
out_path = fs.unique_path(dest_dir, base_name, ext)
|
|
160
|
+
try:
|
|
161
|
+
self._pack_tar_gz(paths, out_path)
|
|
162
|
+
return out_path
|
|
163
|
+
except Exception as e:
|
|
164
|
+
try:
|
|
165
|
+
self.window.core.debug.log(e)
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
return ""
|
|
169
|
+
else:
|
|
170
|
+
return ""
|
|
171
|
+
|
|
172
|
+
def _pack_zip(self, paths: list, out_path: str):
|
|
173
|
+
"""
|
|
174
|
+
Create ZIP archive with selected paths, preserving top-level names.
|
|
175
|
+
"""
|
|
176
|
+
import zipfile
|
|
177
|
+
with zipfile.ZipFile(out_path, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
|
|
178
|
+
for src in paths:
|
|
179
|
+
top = os.path.basename(src.rstrip(os.sep))
|
|
180
|
+
if os.path.isdir(src):
|
|
181
|
+
for root, dirs, files in os.walk(src):
|
|
182
|
+
rel = os.path.relpath(root, src)
|
|
183
|
+
arc_root = top if rel == "." else os.path.join(top, rel)
|
|
184
|
+
# Add empty directories explicitly
|
|
185
|
+
if not files and not dirs:
|
|
186
|
+
zinfo = zipfile.ZipInfo(arc_root + "/")
|
|
187
|
+
zf.writestr(zinfo, "")
|
|
188
|
+
for f in files:
|
|
189
|
+
absf = os.path.join(root, f)
|
|
190
|
+
arcf = os.path.join(arc_root, f)
|
|
191
|
+
zf.write(absf, arcf)
|
|
192
|
+
else:
|
|
193
|
+
zf.write(src, top)
|
|
194
|
+
|
|
195
|
+
def _pack_tar_gz(self, paths: list, out_path: str):
|
|
196
|
+
"""
|
|
197
|
+
Create TAR.GZ archive with selected paths, preserving top-level names.
|
|
198
|
+
"""
|
|
199
|
+
import tarfile
|
|
200
|
+
with tarfile.open(out_path, 'w:gz') as tf:
|
|
201
|
+
for src in paths:
|
|
202
|
+
top = os.path.basename(src.rstrip(os.sep))
|
|
203
|
+
tf.add(src, arcname=top, recursive=True)
|
|
204
|
+
|
|
205
|
+
def unpack_to_dir(self, path: str, dest_dir: str) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Unpack archive at 'path' into 'dest_dir'.
|
|
208
|
+
|
|
209
|
+
:param path: archive file path
|
|
210
|
+
:param dest_dir: destination directory to extract into
|
|
211
|
+
:return: dest_dir on success, empty string otherwise
|
|
212
|
+
"""
|
|
213
|
+
if not self.can_unpack(path):
|
|
214
|
+
return ""
|
|
215
|
+
try:
|
|
216
|
+
import zipfile, tarfile
|
|
217
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
218
|
+
kind = self._detect_kind(path)
|
|
219
|
+
if kind == 'zip':
|
|
220
|
+
with zipfile.ZipFile(path, 'r') as zf:
|
|
221
|
+
zf.extractall(dest_dir)
|
|
222
|
+
elif kind == 'tar':
|
|
223
|
+
with tarfile.open(path, 'r:*') as tf:
|
|
224
|
+
tf.extractall(dest_dir)
|
|
225
|
+
else:
|
|
226
|
+
return ""
|
|
227
|
+
return dest_dir
|
|
228
|
+
except Exception as e:
|
|
229
|
+
try:
|
|
230
|
+
self.window.core.debug.log(e)
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
return ""
|
|
234
|
+
|
|
235
|
+
def unpack_to_sibling_dir(self, path: str) -> str:
|
|
236
|
+
"""
|
|
237
|
+
Unpack archive into directory placed next to the archive, named after archive base name.
|
|
238
|
+
|
|
239
|
+
:param path: archive file path
|
|
240
|
+
:return: created directory path or empty string
|
|
241
|
+
"""
|
|
242
|
+
if not (path and os.path.isfile(path)):
|
|
243
|
+
return ""
|
|
244
|
+
parent = os.path.dirname(path)
|
|
245
|
+
base = self.window.core.filesystem.strip_archive_name(os.path.basename(path))
|
|
246
|
+
out_dir = self.window.core.filesystem.unique_dir(parent, base)
|
|
247
|
+
return self.unpack_to_dir(path, out_dir)
|
pygpt_net/core/idx/idx.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.27 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
@@ -505,16 +505,17 @@ class Idx:
|
|
|
505
505
|
self.llm.get_service_context(stream=False) # init environment only (ENV API keys, etc.)
|
|
506
506
|
store_id = self.get_current_store()
|
|
507
507
|
if store_id in self.items and idx in self.items[store_id]:
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
508
|
+
for basename in list(self.items[store_id][idx].items.keys()):
|
|
509
|
+
item = self.items[store_id][idx].items[basename]
|
|
510
|
+
if file == item["path"]:
|
|
511
|
+
doc_id = item ["id"]
|
|
512
|
+
self.storage.remove_document(
|
|
513
|
+
id=idx,
|
|
514
|
+
doc_id=doc_id,
|
|
515
|
+
)
|
|
516
|
+
# remove from index data and db
|
|
517
|
+
del self.items[store_id][idx].items[basename]
|
|
518
|
+
self.files.remove(store_id, idx, doc_id)
|
|
518
519
|
|
|
519
520
|
def load(self):
|
|
520
521
|
"""Load indexes and indexed items"""
|
pygpt_net/core/idx/worker.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.27 19:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import QObject, Signal, QRunnable, Slot
|
|
@@ -29,6 +29,7 @@ class IndexWorker(QRunnable):
|
|
|
29
29
|
self.replace = None
|
|
30
30
|
self.recursive = None
|
|
31
31
|
self.from_ts = 0
|
|
32
|
+
self.from_ts_batch = {}
|
|
32
33
|
self.idx = None
|
|
33
34
|
self.type = None
|
|
34
35
|
self.silent = False
|
|
@@ -70,6 +71,17 @@ class IndexWorker(QRunnable):
|
|
|
70
71
|
self.content,
|
|
71
72
|
self.from_ts,
|
|
72
73
|
)
|
|
74
|
+
elif self.type == "db_meta_batch":
|
|
75
|
+
result = 0
|
|
76
|
+
errors = []
|
|
77
|
+
for meta_id in self.content:
|
|
78
|
+
r, e = self.window.core.idx.index_db_by_meta_id(
|
|
79
|
+
self.idx,
|
|
80
|
+
meta_id,
|
|
81
|
+
self.from_ts_batch[meta_id],
|
|
82
|
+
)
|
|
83
|
+
result += r
|
|
84
|
+
errors.extend(e)
|
|
73
85
|
elif self.type == "db_current":
|
|
74
86
|
result, errors = self.window.core.idx.index_db_from_updated_ts(
|
|
75
87
|
self.idx,
|
pygpt_net/core/image/image.py
CHANGED
|
@@ -152,7 +152,7 @@ class Image(QObject):
|
|
|
152
152
|
"""
|
|
153
153
|
return {
|
|
154
154
|
"type": "combo",
|
|
155
|
-
"
|
|
155
|
+
#"search": False,
|
|
156
156
|
"label": "img_resolution",
|
|
157
157
|
"value": "1024x1024",
|
|
158
158
|
"keys": self.get_available_resolutions(),
|
|
@@ -166,7 +166,7 @@ class Image(QObject):
|
|
|
166
166
|
"""
|
|
167
167
|
return {
|
|
168
168
|
"type": "combo",
|
|
169
|
-
"
|
|
169
|
+
#"search": False,
|
|
170
170
|
"label": "img_mode",
|
|
171
171
|
"value": "image",
|
|
172
172
|
"keys": self.get_available_modes(),
|
pygpt_net/core/models/models.py
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
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.26 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
13
|
-
from typing import Optional, List, Dict
|
|
13
|
+
from typing import Optional, List, Dict, Tuple
|
|
14
14
|
|
|
15
15
|
from httpx_socks import SyncProxyTransport
|
|
16
16
|
from openai import DefaultHttpxClient
|
|
@@ -272,7 +272,7 @@ class Models:
|
|
|
272
272
|
"""
|
|
273
273
|
return self.multimodal
|
|
274
274
|
|
|
275
|
-
def create_empty(self, append: bool = True) -> ModelItem:
|
|
275
|
+
def create_empty(self, append: bool = True) -> Tuple[ModelItem, str]:
|
|
276
276
|
"""
|
|
277
277
|
Create new empty model
|
|
278
278
|
|
|
@@ -288,7 +288,7 @@ class Models:
|
|
|
288
288
|
model.output = ["text"]
|
|
289
289
|
if append:
|
|
290
290
|
self.items[id] = model
|
|
291
|
-
return model
|
|
291
|
+
return model, id
|
|
292
292
|
|
|
293
293
|
def get_all(self) -> Dict[str, ModelItem]:
|
|
294
294
|
"""
|
|
@@ -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: 2025.12.28 04:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -116,9 +116,19 @@ class Profile:
|
|
|
116
116
|
'profiles': self.profiles
|
|
117
117
|
}
|
|
118
118
|
json_data = json.dumps(config, indent=4)
|
|
119
|
+
f_lock = os.path.join(self.base_workdir, self.PROFILE_FILE + '.lock')
|
|
119
120
|
f = os.path.join(self.base_workdir, self.PROFILE_FILE)
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
# check lock first
|
|
122
|
+
if os.path.exists(f_lock):
|
|
123
|
+
print("WARNING: Profile save aborted, lock exists. Please remove lock file:", f_lock)
|
|
124
|
+
return # abort if lock exists
|
|
125
|
+
with open(f_lock, 'w', encoding='utf-8') as file:
|
|
126
|
+
file.write('lock')
|
|
127
|
+
with open(f, 'w', encoding='utf-8') as file:
|
|
128
|
+
file.write(json_data)
|
|
129
|
+
# remove lock
|
|
130
|
+
if os.path.exists(f_lock):
|
|
131
|
+
os.remove(f_lock)
|
|
122
132
|
|
|
123
133
|
def add(
|
|
124
134
|
self,
|
pygpt_net/core/video/video.py
CHANGED
|
@@ -276,7 +276,7 @@ class Video(QObject):
|
|
|
276
276
|
"""
|
|
277
277
|
return {
|
|
278
278
|
"type": "combo",
|
|
279
|
-
|
|
279
|
+
# "search": False,
|
|
280
280
|
"label": "video.aspect_ratio",
|
|
281
281
|
"value": "16:9",
|
|
282
282
|
"keys": self.get_available_aspect_ratio(),
|
|
@@ -290,7 +290,7 @@ class Video(QObject):
|
|
|
290
290
|
"""
|
|
291
291
|
return {
|
|
292
292
|
"type": "combo",
|
|
293
|
-
|
|
293
|
+
# "search": False,
|
|
294
294
|
"label": "video.resolution",
|
|
295
295
|
"value": "720p",
|
|
296
296
|
"keys": self.get_available_resolutions(),
|
|
@@ -304,7 +304,6 @@ class Video(QObject):
|
|
|
304
304
|
"""
|
|
305
305
|
return {
|
|
306
306
|
"type": "int",
|
|
307
|
-
"slider": False,
|
|
308
307
|
"label": "video.duration",
|
|
309
308
|
"value": 8,
|
|
310
309
|
"placeholder": "s",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"__meta__": {
|
|
3
|
-
"version": "2.
|
|
4
|
-
"app.version": "2.
|
|
5
|
-
"updated_at": "2025-12-
|
|
3
|
+
"version": "2.7.1",
|
|
4
|
+
"app.version": "2.7.1",
|
|
5
|
+
"updated_at": "2025-12-28T00:00:00"
|
|
6
6
|
},
|
|
7
7
|
"access.audio.event.speech": false,
|
|
8
8
|
"access.audio.event.speech.disabled": [],
|
|
@@ -174,4 +174,49 @@ NodeEditor {{
|
|
|
174
174
|
/* Status Bar */
|
|
175
175
|
#StatusBarTimer {{
|
|
176
176
|
color: #999 !important;
|
|
177
|
+
}}
|
|
178
|
+
|
|
179
|
+
/* ComboBox */
|
|
180
|
+
QComboBox QAbstractItemView {{
|
|
181
|
+
border: 1px solid rgba(0,0,0,0.18);
|
|
182
|
+
}}
|
|
183
|
+
|
|
184
|
+
QListView#ComboPopupList {{
|
|
185
|
+
border-radius: 10px;
|
|
186
|
+
background: #1f2123;
|
|
187
|
+
border: 2px solid #4f5b62;
|
|
188
|
+
}}
|
|
189
|
+
|
|
190
|
+
QComboBox QAbstractItemView::item,
|
|
191
|
+
QListView#ComboPopupList::item {{
|
|
192
|
+
min-height: 20px;
|
|
193
|
+
padding: 8px 33px;
|
|
194
|
+
margin: 0;
|
|
195
|
+
color: #fff;
|
|
196
|
+
}}
|
|
197
|
+
|
|
198
|
+
QComboBox QAbstractItemView::item:selected,
|
|
199
|
+
QListView#ComboPopupList::item:selected {{
|
|
200
|
+
background: #fff;
|
|
201
|
+
color: #000;
|
|
202
|
+
selection-color: #000;
|
|
203
|
+
font-weight: bold;
|
|
204
|
+
}}
|
|
205
|
+
|
|
206
|
+
QListView#ComboPopupList::item:disabled {{
|
|
207
|
+
color: #c9c9c9;
|
|
208
|
+
background: #363d42;
|
|
209
|
+
padding: 15px;
|
|
210
|
+
}}
|
|
211
|
+
|
|
212
|
+
QComboBox QAbstractItemView::item:hover,
|
|
213
|
+
QListView#ComboPopupList::item:hover {{ }}
|
|
214
|
+
|
|
215
|
+
QWidget#ComboPopupWindow {{
|
|
216
|
+
background: transparent;
|
|
217
|
+
border: 0;
|
|
218
|
+
}}
|
|
219
|
+
QListView#ComboPopupList > QWidget#ComboPopupViewport {{
|
|
220
|
+
background: transparent;
|
|
221
|
+
border-radius: 0px;
|
|
177
222
|
}}
|