pygpt-net 2.6.67__py3-none-any.whl → 2.7.0__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 +12 -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 +182 -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/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 +2 -1
- pygpt_net/core/idx/idx.py +12 -11
- pygpt_net/core/idx/worker.py +13 -1
- pygpt_net/core/models/models.py +4 -4
- pygpt_net/core/profile/profile.py +13 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/style.dark.css +39 -1
- pygpt_net/data/css/style.light.css +39 -1
- pygpt_net/data/locale/locale.de.ini +3 -1
- pygpt_net/data/locale/locale.en.ini +3 -1
- pygpt_net/data/locale/locale.es.ini +3 -1
- pygpt_net/data/locale/locale.fr.ini +3 -1
- pygpt_net/data/locale/locale.it.ini +3 -1
- pygpt_net/data/locale/locale.pl.ini +4 -2
- pygpt_net/data/locale/locale.uk.ini +3 -1
- pygpt_net/data/locale/locale.zh.ini +3 -1
- pygpt_net/provider/api/openai/__init__.py +4 -2
- pygpt_net/provider/core/config/patch.py +9 -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/layout/ctx/ctx_list.py +16 -6
- pygpt_net/ui/main.py +3 -1
- pygpt_net/ui/widget/calendar/select.py +3 -3
- pygpt_net/ui/widget/filesystem/explorer.py +1082 -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 +1046 -32
- pygpt_net/ui/widget/option/dictionary.py +35 -7
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/METADATA +14 -57
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/RECORD +69 -69
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,12 +6,12 @@
|
|
|
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.28 04:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Optional, Dict, Any
|
|
14
|
+
from typing import Optional, Dict, Any, Union
|
|
15
15
|
|
|
16
16
|
from PySide6.QtCore import Slot, QTimer
|
|
17
17
|
from PySide6.QtGui import QAction
|
|
@@ -173,6 +173,8 @@ class Profile:
|
|
|
173
173
|
self.setup()
|
|
174
174
|
self.initialized = True
|
|
175
175
|
if not self.dialog or force:
|
|
176
|
+
self.update_menu()
|
|
177
|
+
self.update_list()
|
|
176
178
|
self.window.ui.dialogs.open(
|
|
177
179
|
'profile.editor',
|
|
178
180
|
width=self.width,
|
|
@@ -336,18 +338,26 @@ class Profile:
|
|
|
336
338
|
uuid = self.get_id_by_idx(idx)
|
|
337
339
|
self.switch(uuid)
|
|
338
340
|
|
|
339
|
-
def delete_by_idx(self, idx: int, force: bool = False):
|
|
341
|
+
def delete_by_idx(self, idx: Union[int, list], force: bool = False):
|
|
340
342
|
"""
|
|
341
343
|
Delete profile by index
|
|
342
344
|
|
|
343
|
-
:param idx: profile index
|
|
345
|
+
:param idx: profile index or list of indexes
|
|
344
346
|
:param force: force delete
|
|
345
347
|
"""
|
|
346
|
-
|
|
348
|
+
uuids = []
|
|
349
|
+
ids = idx if isinstance(idx, list) else [idx]
|
|
350
|
+
for i in ids:
|
|
351
|
+
uuid = self.get_id_by_idx(i)
|
|
352
|
+
uuids.append(uuid)
|
|
347
353
|
current = self.window.core.config.profile.get_current()
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
354
|
+
for uuid in list(uuids):
|
|
355
|
+
if uuid == current:
|
|
356
|
+
if len(uuids) == 1:
|
|
357
|
+
self.window.ui.dialogs.alert(trans("dialog.profile.alert.delete.current"))
|
|
358
|
+
return
|
|
359
|
+
else:
|
|
360
|
+
uuids.remove(uuid) # skip current
|
|
351
361
|
if not force:
|
|
352
362
|
self.window.ui.dialogs.confirm(
|
|
353
363
|
type='profile.delete',
|
|
@@ -355,35 +365,48 @@ class Profile:
|
|
|
355
365
|
msg=trans('confirm.profile.delete'),
|
|
356
366
|
)
|
|
357
367
|
return
|
|
358
|
-
self.delete(
|
|
368
|
+
self.delete(uuids)
|
|
359
369
|
|
|
360
|
-
def delete(self, uuid: str):
|
|
370
|
+
def delete(self, uuid: Union[str, list]):
|
|
361
371
|
"""
|
|
362
372
|
Delete profile (remove only)
|
|
363
373
|
|
|
364
|
-
:param uuid: profile ID
|
|
374
|
+
:param uuid: profile ID or list of IDs
|
|
365
375
|
"""
|
|
366
376
|
profiles = self.get_profiles()
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
self.
|
|
377
|
+
updated = False
|
|
378
|
+
ids = uuid if isinstance(uuid, list) else [uuid]
|
|
379
|
+
for uuid in ids:
|
|
380
|
+
if uuid in profiles:
|
|
381
|
+
profile = profiles[uuid]
|
|
382
|
+
name = profile['name']
|
|
383
|
+
if self.window.core.config.profile.remove(uuid):
|
|
384
|
+
updated = True
|
|
385
|
+
self.window.update_status(trans("dialog.profile.status.removed") + ": " + name)
|
|
386
|
+
if updated:
|
|
387
|
+
self.update_list()
|
|
388
|
+
self.update_menu()
|
|
374
389
|
|
|
375
|
-
def delete_all_by_idx(self, idx: int, force: bool = False):
|
|
390
|
+
def delete_all_by_idx(self, idx: Union[int, list], force: bool = False):
|
|
376
391
|
"""
|
|
377
392
|
Delete profile with files by index
|
|
378
393
|
|
|
379
|
-
:param idx: profile index
|
|
394
|
+
:param idx: profile index or list of indexes
|
|
380
395
|
:param force: force delete
|
|
381
396
|
"""
|
|
382
|
-
|
|
397
|
+
uuids = []
|
|
398
|
+
ids = idx if isinstance(idx, list) else [idx]
|
|
399
|
+
for i in ids:
|
|
400
|
+
uuid = self.get_id_by_idx(i)
|
|
401
|
+
uuids.append(uuid)
|
|
383
402
|
current = self.window.core.config.profile.get_current()
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
403
|
+
for uuid in list(uuids):
|
|
404
|
+
if uuid == current:
|
|
405
|
+
if len(uuids) == 1:
|
|
406
|
+
self.window.ui.dialogs.alert(trans("dialog.profile.alert.delete.current"))
|
|
407
|
+
return
|
|
408
|
+
else:
|
|
409
|
+
uuids.remove(uuid) # skip current
|
|
387
410
|
if not force:
|
|
388
411
|
self.window.ui.dialogs.confirm(
|
|
389
412
|
type='profile.delete.all',
|
|
@@ -391,7 +414,7 @@ class Profile:
|
|
|
391
414
|
msg=trans('confirm.profile.delete_all'),
|
|
392
415
|
)
|
|
393
416
|
return
|
|
394
|
-
self.delete_all(
|
|
417
|
+
self.delete_all(uuids)
|
|
395
418
|
|
|
396
419
|
def duplicate_by_idx(self, idx: int):
|
|
397
420
|
"""
|
|
@@ -416,13 +439,20 @@ class Profile:
|
|
|
416
439
|
dialog.prepare()
|
|
417
440
|
dialog.show()
|
|
418
441
|
|
|
419
|
-
def delete_all(self, uuid: str):
|
|
442
|
+
def delete_all(self, uuid: Union[str, list]):
|
|
420
443
|
"""
|
|
421
444
|
Delete profile with files
|
|
422
445
|
|
|
423
|
-
:param uuid: profile ID
|
|
446
|
+
:param uuid: profile ID or list of IDs
|
|
424
447
|
"""
|
|
425
|
-
|
|
448
|
+
ids = uuid if isinstance(uuid, list) else [uuid]
|
|
449
|
+
batch = False
|
|
450
|
+
if len(ids) > 1:
|
|
451
|
+
batch = True
|
|
452
|
+
if not batch:
|
|
453
|
+
self.window.controller.settings.workdir.delete_files(uuid)
|
|
454
|
+
else:
|
|
455
|
+
self.window.controller.settings.workdir.delete_files(ids, batch=batch)
|
|
426
456
|
|
|
427
457
|
@Slot(str)
|
|
428
458
|
def after_delete(self, name: str):
|
|
@@ -468,22 +498,33 @@ class Profile:
|
|
|
468
498
|
if self.window.ui.nodes['dialog.profile.checkbox.switch'].isChecked():
|
|
469
499
|
self.switch(uuid, force=True)
|
|
470
500
|
|
|
471
|
-
def reset(self, uuid: str):
|
|
501
|
+
def reset(self, uuid: Union[str, list]):
|
|
472
502
|
"""
|
|
473
503
|
Reset profile
|
|
474
504
|
|
|
475
|
-
:param uuid: profile ID
|
|
505
|
+
:param uuid: profile ID or list of IDs
|
|
476
506
|
"""
|
|
477
|
-
|
|
507
|
+
ids = uuid if isinstance(uuid, list) else [uuid]
|
|
508
|
+
batch = False
|
|
509
|
+
if len(ids) > 1:
|
|
510
|
+
batch = True
|
|
511
|
+
if not batch:
|
|
512
|
+
self.window.controller.settings.workdir.reset(uuid)
|
|
513
|
+
else:
|
|
514
|
+
self.window.controller.settings.workdir.reset(ids, batch=batch)
|
|
478
515
|
|
|
479
|
-
def reset_by_idx(self, idx: int, force: bool = False):
|
|
516
|
+
def reset_by_idx(self, idx: Union[int, list], force: bool = False):
|
|
480
517
|
"""
|
|
481
518
|
Reset profile by index
|
|
482
519
|
|
|
483
|
-
:param idx: profile index
|
|
520
|
+
:param idx: profile index or list of indexes
|
|
484
521
|
:param force: force reset
|
|
485
522
|
"""
|
|
486
|
-
|
|
523
|
+
uuids = []
|
|
524
|
+
ids = idx if isinstance(idx, list) else [idx]
|
|
525
|
+
for i in ids:
|
|
526
|
+
uuid = self.get_id_by_idx(i)
|
|
527
|
+
uuids.append(uuid)
|
|
487
528
|
if not force:
|
|
488
529
|
self.window.ui.dialogs.confirm(
|
|
489
530
|
type='profile.reset',
|
|
@@ -491,7 +532,7 @@ class Profile:
|
|
|
491
532
|
msg=trans('confirm.profile.reset'),
|
|
492
533
|
)
|
|
493
534
|
return
|
|
494
|
-
self.reset(
|
|
535
|
+
self.reset(uuids)
|
|
495
536
|
|
|
496
537
|
def is_include_db(self):
|
|
497
538
|
"""Get include db"""
|
|
@@ -6,13 +6,13 @@
|
|
|
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.28 04:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
13
13
|
import os
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Optional
|
|
15
|
+
from typing import Optional, Union
|
|
16
16
|
from uuid import uuid4
|
|
17
17
|
|
|
18
18
|
from PySide6.QtCore import QObject, QRunnable, Signal, Slot
|
|
@@ -50,9 +50,10 @@ class WorkdirWorker(QRunnable):
|
|
|
50
50
|
force: bool = False,
|
|
51
51
|
current: Optional[str] = None,
|
|
52
52
|
profile_name: Optional[str] = None,
|
|
53
|
-
profile_uuid: Optional[str] = None,
|
|
53
|
+
profile_uuid: Optional[Union[str, list]] = None,
|
|
54
54
|
profile_new_name: Optional[str] = None,
|
|
55
55
|
profile_new_path: Optional[str] = None,
|
|
56
|
+
batch: bool = False,
|
|
56
57
|
):
|
|
57
58
|
super().__init__()
|
|
58
59
|
self.window = window
|
|
@@ -65,6 +66,7 @@ class WorkdirWorker(QRunnable):
|
|
|
65
66
|
self.profile_new_name = profile_new_name
|
|
66
67
|
self.profile_new_path = profile_new_path
|
|
67
68
|
self.signals = WorkerSignals()
|
|
69
|
+
self.batch = batch
|
|
68
70
|
|
|
69
71
|
@Slot()
|
|
70
72
|
def run(self):
|
|
@@ -104,22 +106,34 @@ class WorkdirWorker(QRunnable):
|
|
|
104
106
|
profiles = self.window.controller.settings.profile.get_profiles()
|
|
105
107
|
remove_datadir = True
|
|
106
108
|
remove_db = True
|
|
107
|
-
if uuid
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
109
|
+
uuids = uuid if isinstance(uuid, list) else [uuid]
|
|
110
|
+
for uuid in uuids:
|
|
111
|
+
if uuid in profiles:
|
|
112
|
+
profile = profiles[uuid]
|
|
113
|
+
name = profile['name']
|
|
114
|
+
path = profile['workdir'].replace("%HOME%", str(Path.home()))
|
|
115
|
+
# remove profile
|
|
116
|
+
if self.window.core.config.profile.remove(uuid):
|
|
117
|
+
if not os.path.exists(path) or not os.path.isdir(path):
|
|
118
|
+
if not self.batch:
|
|
119
|
+
self.signals.deleted.emit(name)
|
|
120
|
+
self.signals.alert.emit(trans("dialog.profile.alert.path.not_exists"))
|
|
121
|
+
return
|
|
122
|
+
else:
|
|
123
|
+
print("Directory not exists, ignoring: ", path)
|
|
124
|
+
self.signals.updateGlobalStatus.emit(f"Directory not exists: {path}")
|
|
125
|
+
self.signals.deleted.emit(name)
|
|
126
|
+
return # nothing to delete
|
|
127
|
+
print(f"Clearing workdir: {path}")
|
|
128
|
+
try:
|
|
129
|
+
self.window.core.filesystem.clear_workdir(
|
|
130
|
+
path,
|
|
131
|
+
remove_db=remove_db,
|
|
132
|
+
remove_datadir=remove_datadir,
|
|
133
|
+
)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print("Error deleting profile files: ", e)
|
|
136
|
+
self.signals.deleted.emit(name)
|
|
123
137
|
|
|
124
138
|
def worker_duplicate(self):
|
|
125
139
|
"""Duplicate profile"""
|
|
@@ -168,22 +182,30 @@ class WorkdirWorker(QRunnable):
|
|
|
168
182
|
current = self.window.core.config.profile.get_current()
|
|
169
183
|
remove_datadir = False
|
|
170
184
|
remove_db = True
|
|
171
|
-
if uuid
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
uuids = uuid if isinstance(uuid, list) else [uuid]
|
|
186
|
+
for uuid in uuids:
|
|
187
|
+
if uuid in profiles:
|
|
188
|
+
profile = profiles[uuid]
|
|
189
|
+
path = profile['workdir'].replace("%HOME%", str(Path.home()))
|
|
190
|
+
if not os.path.exists(path) or not os.path.isdir(path):
|
|
191
|
+
if not self.batch:
|
|
192
|
+
self.signals.alert.emit(f"Directory not exists: {path}")
|
|
193
|
+
return
|
|
194
|
+
else:
|
|
195
|
+
print("Directory not exists, ignoring: ", path)
|
|
196
|
+
self.signals.updateGlobalStatus.emit(f"Directory not exists: {path}")
|
|
197
|
+
return
|
|
198
|
+
print("Clearing workdir: ", path)
|
|
199
|
+
if uuid == current:
|
|
200
|
+
self.window.core.db.close()
|
|
201
|
+
self.window.core.filesystem.clear_workdir(
|
|
202
|
+
path,
|
|
203
|
+
remove_db=remove_db,
|
|
204
|
+
remove_datadir=remove_datadir,
|
|
205
|
+
)
|
|
206
|
+
if uuid == current and not self.batch:
|
|
207
|
+
self.signals.switch.emit(uuid) # switch to profile
|
|
208
|
+
self.signals.updateGlobalStatus.emit(f"Profile cleared: {profile['name']}")
|
|
187
209
|
|
|
188
210
|
def worker_migrate(self):
|
|
189
211
|
"""Migrate working directory"""
|
|
@@ -471,9 +493,10 @@ class Workdir:
|
|
|
471
493
|
force: bool = False,
|
|
472
494
|
current: str = None,
|
|
473
495
|
profile_name: Optional[str] = None,
|
|
474
|
-
profile_uuid: Optional[str] = None,
|
|
496
|
+
profile_uuid: Optional[Union[str, list]] = None,
|
|
475
497
|
profile_new_name: Optional[str] = None,
|
|
476
498
|
profile_new_path: Optional[str] = None,
|
|
499
|
+
batch: bool = False,
|
|
477
500
|
):
|
|
478
501
|
"""
|
|
479
502
|
Run action in thread
|
|
@@ -483,9 +506,10 @@ class Workdir:
|
|
|
483
506
|
:param force: force action (confirm)
|
|
484
507
|
:param current: current working directory (for restore action)
|
|
485
508
|
:param profile_name: profile name (optional, for after update callback)
|
|
486
|
-
:param profile_uuid: profile UUID (optional, for delete files action)
|
|
509
|
+
:param profile_uuid: profile UUID (optional, for delete files action) or list of UUIDs
|
|
487
510
|
:param profile_new_name: new profile name (optional, for duplicate action)
|
|
488
511
|
:param profile_new_path: new profile path (optional, for duplicate action)
|
|
512
|
+
:param batch: if True, run in batch mode (no UI updates)
|
|
489
513
|
"""
|
|
490
514
|
worker = WorkdirWorker(
|
|
491
515
|
window=self.window,
|
|
@@ -497,6 +521,7 @@ class Workdir:
|
|
|
497
521
|
profile_uuid=profile_uuid,
|
|
498
522
|
profile_new_name=profile_new_name,
|
|
499
523
|
profile_new_path=profile_new_path,
|
|
524
|
+
batch=batch,
|
|
500
525
|
)
|
|
501
526
|
|
|
502
527
|
# connect signals
|
|
@@ -569,16 +594,19 @@ class Workdir:
|
|
|
569
594
|
|
|
570
595
|
def delete_files(
|
|
571
596
|
self,
|
|
572
|
-
profile_uuid: str
|
|
597
|
+
profile_uuid: Union[str, list],
|
|
598
|
+
batch: bool = False
|
|
573
599
|
):
|
|
574
600
|
"""
|
|
575
601
|
Delete files and directories associated with the profile
|
|
576
602
|
|
|
577
603
|
:param profile_uuid: profile UUID
|
|
604
|
+
:param batch: if True, run in batch mode (no UI updates)
|
|
578
605
|
"""
|
|
579
606
|
self.run_action(
|
|
580
607
|
action="delete",
|
|
581
608
|
profile_uuid=profile_uuid,
|
|
609
|
+
batch=batch,
|
|
582
610
|
)
|
|
583
611
|
|
|
584
612
|
def duplicate(
|
|
@@ -603,14 +631,17 @@ class Workdir:
|
|
|
603
631
|
|
|
604
632
|
def reset(
|
|
605
633
|
self,
|
|
606
|
-
profile_uuid: str
|
|
634
|
+
profile_uuid: Union[str, list],
|
|
635
|
+
batch: bool = False
|
|
607
636
|
):
|
|
608
637
|
"""
|
|
609
638
|
Reset profile
|
|
610
639
|
|
|
611
640
|
:param profile_uuid: profile UUID to reset
|
|
641
|
+
:param batch: if True, run in batch mode (no UI updates)
|
|
612
642
|
"""
|
|
613
643
|
self.run_action(
|
|
614
644
|
action="reset",
|
|
615
645
|
profile_uuid=profile_uuid,
|
|
646
|
+
batch=batch,
|
|
616
647
|
)
|
|
@@ -6,10 +6,10 @@
|
|
|
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.27 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from typing import Optional, List, Dict
|
|
12
|
+
from typing import Optional, List, Dict, Union
|
|
13
13
|
|
|
14
14
|
from packaging.version import Version
|
|
15
15
|
|
|
@@ -231,30 +231,32 @@ class Files:
|
|
|
231
231
|
"""
|
|
232
232
|
return self.provider.get_all_by_file_id(file_id)
|
|
233
233
|
|
|
234
|
-
def delete(self, file: AssistantFileItem) -> bool:
|
|
234
|
+
def delete(self, file: Union[AssistantFileItem, list]) -> bool:
|
|
235
235
|
"""
|
|
236
236
|
Delete file and remove from vector stores if exists
|
|
237
237
|
|
|
238
238
|
:param file: file item
|
|
239
239
|
:return: True if file was deleted
|
|
240
240
|
"""
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
files = file if isinstance(file, list) else [file]
|
|
242
|
+
for file in files:
|
|
243
|
+
file_id = file.file_id
|
|
244
|
+
items = self.get_all_by_file_id(file_id) # get store_ids
|
|
245
|
+
for id in items:
|
|
246
|
+
store_id = items[id].store_id
|
|
247
|
+
if store_id is None or store_id == "":
|
|
248
|
+
continue # skip if no store_id
|
|
249
|
+
try:
|
|
250
|
+
self.window.core.api.openai.store.delete_store_file(store_id, file_id) # remove from vector store
|
|
251
|
+
except Exception as e:
|
|
252
|
+
self.window.core.debug.log("Failed to delete file from vector store: " + str(e))
|
|
253
|
+
self.provider.delete_by_id(file.record_id) # delete file in DB
|
|
247
254
|
try:
|
|
248
|
-
self.window.core.api.openai.store.
|
|
255
|
+
self.window.core.api.openai.store.delete_file(file.file_id) # delete file in API
|
|
249
256
|
except Exception as e:
|
|
250
|
-
self.window.core.debug.log("Failed to delete file
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
self.window.core.api.openai.store.delete_file(file.file_id) # delete file in API
|
|
254
|
-
except Exception as e:
|
|
255
|
-
self.window.core.debug.log("Failed to delete remote file: " + str(e))
|
|
256
|
-
if file.record_id in self.items:
|
|
257
|
-
del self.items[file.record_id]
|
|
257
|
+
self.window.core.debug.log("Failed to delete remote file: " + str(e))
|
|
258
|
+
if file.record_id in self.items:
|
|
259
|
+
del self.items[file.record_id]
|
|
258
260
|
return True
|
|
259
261
|
|
|
260
262
|
def delete_by_file_id(self, file_id: str) -> bool:
|
|
@@ -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(
|