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.
Files changed (69) hide show
  1. pygpt_net/CHANGELOG.txt +12 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/assistant/assistant.py +13 -8
  4. pygpt_net/controller/assistant/batch.py +29 -15
  5. pygpt_net/controller/assistant/files.py +19 -14
  6. pygpt_net/controller/assistant/store.py +63 -41
  7. pygpt_net/controller/attachment/attachment.py +45 -35
  8. pygpt_net/controller/chat/attachment.py +50 -39
  9. pygpt_net/controller/config/field/dictionary.py +26 -14
  10. pygpt_net/controller/ctx/common.py +27 -17
  11. pygpt_net/controller/ctx/ctx.py +182 -101
  12. pygpt_net/controller/files/files.py +101 -41
  13. pygpt_net/controller/idx/indexer.py +87 -31
  14. pygpt_net/controller/kernel/kernel.py +13 -2
  15. pygpt_net/controller/mode/mode.py +3 -3
  16. pygpt_net/controller/model/editor.py +70 -15
  17. pygpt_net/controller/model/importer.py +153 -54
  18. pygpt_net/controller/painter/painter.py +2 -2
  19. pygpt_net/controller/presets/experts.py +68 -15
  20. pygpt_net/controller/presets/presets.py +72 -36
  21. pygpt_net/controller/settings/profile.py +76 -35
  22. pygpt_net/controller/settings/workdir.py +70 -39
  23. pygpt_net/core/assistants/files.py +20 -18
  24. pygpt_net/core/filesystem/actions.py +111 -10
  25. pygpt_net/core/filesystem/filesystem.py +2 -1
  26. pygpt_net/core/idx/idx.py +12 -11
  27. pygpt_net/core/idx/worker.py +13 -1
  28. pygpt_net/core/models/models.py +4 -4
  29. pygpt_net/core/profile/profile.py +13 -3
  30. pygpt_net/data/config/config.json +3 -3
  31. pygpt_net/data/config/models.json +3 -3
  32. pygpt_net/data/css/style.dark.css +39 -1
  33. pygpt_net/data/css/style.light.css +39 -1
  34. pygpt_net/data/locale/locale.de.ini +3 -1
  35. pygpt_net/data/locale/locale.en.ini +3 -1
  36. pygpt_net/data/locale/locale.es.ini +3 -1
  37. pygpt_net/data/locale/locale.fr.ini +3 -1
  38. pygpt_net/data/locale/locale.it.ini +3 -1
  39. pygpt_net/data/locale/locale.pl.ini +4 -2
  40. pygpt_net/data/locale/locale.uk.ini +3 -1
  41. pygpt_net/data/locale/locale.zh.ini +3 -1
  42. pygpt_net/provider/api/openai/__init__.py +4 -2
  43. pygpt_net/provider/core/config/patch.py +9 -1
  44. pygpt_net/tools/image_viewer/tool.py +17 -0
  45. pygpt_net/tools/text_editor/tool.py +9 -0
  46. pygpt_net/ui/__init__.py +2 -2
  47. pygpt_net/ui/layout/ctx/ctx_list.py +16 -6
  48. pygpt_net/ui/main.py +3 -1
  49. pygpt_net/ui/widget/calendar/select.py +3 -3
  50. pygpt_net/ui/widget/filesystem/explorer.py +1082 -142
  51. pygpt_net/ui/widget/lists/assistant.py +185 -24
  52. pygpt_net/ui/widget/lists/assistant_store.py +245 -42
  53. pygpt_net/ui/widget/lists/attachment.py +230 -47
  54. pygpt_net/ui/widget/lists/attachment_ctx.py +189 -33
  55. pygpt_net/ui/widget/lists/base_list_combo.py +2 -2
  56. pygpt_net/ui/widget/lists/context.py +1253 -70
  57. pygpt_net/ui/widget/lists/experts.py +110 -8
  58. pygpt_net/ui/widget/lists/model_editor.py +217 -14
  59. pygpt_net/ui/widget/lists/model_importer.py +125 -6
  60. pygpt_net/ui/widget/lists/preset.py +460 -71
  61. pygpt_net/ui/widget/lists/profile.py +149 -27
  62. pygpt_net/ui/widget/lists/uploaded.py +230 -38
  63. pygpt_net/ui/widget/option/combo.py +1046 -32
  64. pygpt_net/ui/widget/option/dictionary.py +35 -7
  65. {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/METADATA +14 -57
  66. {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/RECORD +69 -69
  67. {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/LICENSE +0 -0
  68. {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/WHEEL +0 -0
  69. {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.0.dist-info}/entry_points.txt +0 -0
@@ -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: 2025.09.16 22:00:00 #
9
+ # Updated Date: 2025.12.27 21:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from typing import Optional, List
12
+ from typing import Optional, List, Union
13
13
 
14
14
  from PySide6.QtCore import QModelIndex, QTimer
15
15
  from PySide6.QtGui import QStandardItem
@@ -273,7 +273,7 @@ class Ctx:
273
273
  self.update()
274
274
  index = self.get_child_index_by_id(id)
275
275
  if index.isValid():
276
- self.window.ui.nodes['ctx.list'].scrollTo(index)
276
+ self.window.ui.nodes['ctx.list'].force_scroll_to_current()
277
277
 
278
278
  def unselect(self):
279
279
  """Unselect ctx"""
@@ -319,7 +319,7 @@ class Ctx:
319
319
  def new(
320
320
  self,
321
321
  force: bool = False,
322
- group_id: Optional[int] = None
322
+ group_id: Optional[Union[int, list]] = None
323
323
  ):
324
324
  """
325
325
  Create new ctx
@@ -330,6 +330,15 @@ class Ctx:
330
330
  if not force and self.context_change_locked():
331
331
  return
332
332
 
333
+ # if multiple group IDs provided, pick the first valid
334
+ if isinstance(group_id, list):
335
+ valid_group_id = None
336
+ for gid in group_id:
337
+ if self.window.core.ctx.has_group(gid):
338
+ valid_group_id = gid
339
+ break
340
+ group_id = valid_group_id
341
+
333
342
  if group_id is None:
334
343
  if self.group_id is not None and self.group_id > 0:
335
344
  group_id = self.group_id
@@ -596,13 +605,13 @@ class Ctx:
596
605
 
597
606
  def delete(
598
607
  self,
599
- id: int,
608
+ id: Union[int, list],
600
609
  force: bool = False
601
610
  ):
602
611
  """
603
612
  Delete ctx by idx
604
613
 
605
- :param id: context meta idx on list
614
+ :param id: context meta idx on list or list of idxs
606
615
  :param force: force delete
607
616
  """
608
617
  if not force:
@@ -612,26 +621,31 @@ class Ctx:
612
621
  msg=trans('ctx.delete.confirm'),
613
622
  )
614
623
  return
615
-
616
- try:
617
- self.delete_meta_from_idx(id)
618
- except Exception as e:
619
- self.window.core.debug.log(e)
620
- print("Error deleting ctx data from indexes", e)
621
-
622
- items = self.window.core.ctx.all()
623
- self.window.core.history.remove_items(items)
624
- self.window.core.attachments.context.delete_by_meta_id(id)
625
- self.window.core.ctx.remove(id)
626
- self.remove_selected(id)
627
-
628
- if self.window.core.ctx.get_current() == id:
629
- self.window.core.ctx.clear_current()
630
- event = RenderEvent(RenderEvent.CLEAR_OUTPUT)
631
- self.window.dispatch(event)
632
- self.update_and_restore()
633
-
634
- self.window.controller.ui.tabs.update_title_current("...")
624
+ updated = False
625
+ ids = id if isinstance(id, list) else [id]
626
+ for id in ids:
627
+ try:
628
+ self.delete_meta_from_idx(id)
629
+ except Exception as e:
630
+ self.window.core.debug.log(e)
631
+ print("Error deleting ctx data from indexes", e)
632
+
633
+ if self.window.core.ctx.get_current() == id:
634
+ items = self.window.core.ctx.all() # TODO: get by meta id(s)
635
+ self.window.core.history.remove_items(items)
636
+ self.window.core.attachments.context.delete_by_meta_id(id)
637
+ self.window.core.ctx.remove(id)
638
+ self.remove_selected(id)
639
+
640
+ if self.window.core.ctx.get_current() == id:
641
+ self.window.core.ctx.clear_current()
642
+ event = RenderEvent(RenderEvent.CLEAR_OUTPUT)
643
+ self.window.dispatch(event)
644
+ updated = True
645
+
646
+ if updated:
647
+ self.update_and_restore()
648
+ self.window.controller.ui.tabs.update_title_current("...")
635
649
 
636
650
  def delete_meta_from_idx(self, id: int):
637
651
  """
@@ -738,13 +752,18 @@ class Ctx:
738
752
  if self.window.core.ctx.count_meta() == 0:
739
753
  self.new()
740
754
 
741
- def rename(self, id: int):
755
+ def rename(self, id: Union[int, list]):
742
756
  """
743
757
  Ctx name rename by id (show dialog)
744
758
 
745
- :param id: context id
759
+ :param id: context id or list of ids
746
760
  """
747
- meta = self.window.core.ctx.get_meta_by_id(id)
761
+ first_id = id
762
+ if isinstance(id, list):
763
+ if len(id) == 0:
764
+ return
765
+ first_id = id[0]
766
+ meta = self.window.core.ctx.get_meta_by_id(first_id)
748
767
  self.window.ui.dialog['rename'].id = 'ctx'
749
768
  self.window.ui.dialog['rename'].input.setText(meta.name)
750
769
  self.window.ui.dialog['rename'].current = id
@@ -752,19 +771,24 @@ class Ctx:
752
771
 
753
772
  def set_important(
754
773
  self,
755
- id: int,
774
+ id: Union[int, list],
756
775
  value: bool = True
757
776
  ):
758
777
  """
759
778
  Set as important
760
779
 
761
- :param id: context idx
780
+ :param id: context idx or list of idxs
762
781
  :param value: important value
763
782
  """
764
- meta = self.window.core.ctx.get_meta_by_id(id)
765
- if meta is not None:
766
- meta.important = value
767
- self.window.core.ctx.save(id)
783
+ updated = False
784
+ ids = id if isinstance(id, list) else [id]
785
+ for id in ids:
786
+ meta = self.window.core.ctx.get_meta_by_id(id)
787
+ if meta is not None:
788
+ meta.important = value
789
+ self.window.core.ctx.save(id)
790
+ updated = True
791
+ if updated:
768
792
  self.update_and_restore()
769
793
 
770
794
  def is_important(self, idx: int) -> bool:
@@ -782,19 +806,25 @@ class Ctx:
782
806
 
783
807
  def set_label(
784
808
  self,
785
- id: int,
809
+ id: Union[int, list],
786
810
  label_id: int
787
811
  ):
788
812
  """
789
813
  Set color label for ctx by idx
790
814
 
791
- :param id: context idx
815
+ :param id: context idx or list of idxs
792
816
  :param label_id: label id
793
817
  """
794
- meta = self.window.core.ctx.get_meta_by_id(id)
795
- if meta is not None:
796
- meta.label = label_id
797
- self.window.core.ctx.save(id)
818
+ updated = False
819
+ ids = id if isinstance(id, list) else [id]
820
+ for id in ids:
821
+ meta = self.window.core.ctx.get_meta_by_id(id)
822
+ if meta is not None:
823
+ meta.label = label_id
824
+ self.window.core.ctx.save(id)
825
+ updated = True
826
+
827
+ if updated:
798
828
  QTimer.singleShot(
799
829
  10,
800
830
  lambda: self.update_and_restore()
@@ -808,7 +838,7 @@ class Ctx:
808
838
 
809
839
  def update_name(
810
840
  self,
811
- id: int,
841
+ id: Union[int, list],
812
842
  name: str,
813
843
  close: bool = True,
814
844
  refresh: bool = True
@@ -816,16 +846,22 @@ class Ctx:
816
846
  """
817
847
  Update ctx name
818
848
 
819
- :param id: context id
849
+ :param id: context id or list of ids
820
850
  :param name: context name
821
851
  :param close: close rename dialog
822
852
  :param refresh: refresh ctx list
823
853
  """
824
- if id not in self.window.core.ctx.get_meta():
825
- return
826
- self.window.core.ctx.meta[id].name = name
827
- self.window.core.ctx.set_initialized()
828
- self.window.core.ctx.save(id)
854
+ ctx_id = id
855
+ if not isinstance(id, list):
856
+ ctx_id = [id]
857
+
858
+ for id in ctx_id:
859
+ if id not in self.window.core.ctx.get_meta():
860
+ continue
861
+ self.window.core.ctx.meta[id].name = name
862
+ self.window.core.ctx.set_initialized()
863
+ self.window.core.ctx.save(id)
864
+
829
865
  if close:
830
866
  self.window.ui.dialog['rename'].close()
831
867
 
@@ -834,10 +870,13 @@ class Ctx:
834
870
  else:
835
871
  self.update(reload=True, all=False, no_scroll=True)
836
872
 
837
- meta = self.window.core.ctx.get_meta_by_id(id)
838
- if meta is not None:
839
- if id == self.window.core.ctx.get_current():
840
- self.window.controller.ui.tabs.update_title_current(meta.name)
873
+ for id in ctx_id:
874
+ if id not in self.window.core.ctx.get_meta():
875
+ continue
876
+ meta = self.window.core.ctx.get_meta_by_id(id)
877
+ if meta is not None:
878
+ if id == self.window.core.ctx.get_current():
879
+ self.window.controller.ui.tabs.update_title_current(meta.name)
841
880
 
842
881
  def update_name_current(self, name: str):
843
882
  """
@@ -934,7 +973,11 @@ class Ctx:
934
973
  index = self.get_child_index_by_id(id)
935
974
  nodes = self.window.ui.nodes
936
975
  nodes['ctx.list'].unlocked = True
976
+ if not index.isValid():
977
+ nodes['ctx.list'].unlocked = False
978
+ return
937
979
  nodes['ctx.list'].setCurrentIndex(index)
980
+ nodes['ctx.list'].force_scroll_to_current()
938
981
  nodes['ctx.list'].unlocked = False
939
982
 
940
983
  def find_index_by_id(
@@ -1067,37 +1110,47 @@ class Ctx:
1067
1110
 
1068
1111
  def move_to_group(
1069
1112
  self,
1070
- meta_id: int,
1113
+ meta_id: Union[int, list],
1071
1114
  group_id: int,
1072
1115
  update: bool = True
1073
1116
  ):
1074
1117
  """
1075
1118
  Move ctx to group
1076
1119
 
1077
- :param meta_id: int
1120
+ :param meta_id: int or list of int
1078
1121
  :param group_id: int
1079
1122
  :param update: update ctx list
1080
1123
  """
1081
- self.window.core.ctx.update_meta_group_id(meta_id, group_id)
1082
- self.group_id = group_id
1083
- if update:
1124
+ updated = False
1125
+ ids = meta_id if isinstance(meta_id, list) else [meta_id]
1126
+ for meta_id in ids:
1127
+ self.window.core.ctx.update_meta_group_id(meta_id, group_id)
1128
+ self.group_id = group_id
1129
+ updated = True
1130
+
1131
+ if updated and update:
1084
1132
  QTimer.singleShot(
1085
1133
  10,
1086
1134
  lambda: self.update_and_restore()
1087
1135
  )
1088
1136
 
1089
- def remove_from_group(self, meta_id):
1137
+ def remove_from_group(self, meta_id: Union[int, list]):
1090
1138
  """
1091
1139
  Remove ctx from group
1092
1140
 
1093
- :param meta_id: int
1141
+ :param meta_id: int or list of int
1094
1142
  """
1095
- self.window.core.ctx.update_meta_group_id(meta_id, None)
1096
- self.group_id = None
1097
- QTimer.singleShot(
1098
- 10,
1099
- lambda: self.update_and_restore()
1100
- )
1143
+ updated = False
1144
+ ids = meta_id if isinstance(meta_id, list) else [meta_id]
1145
+ for meta_id in ids:
1146
+ self.window.core.ctx.update_meta_group_id(meta_id, None)
1147
+ updated = True
1148
+ if updated:
1149
+ self.group_id = None
1150
+ QTimer.singleShot(
1151
+ 10,
1152
+ lambda: self.update_and_restore()
1153
+ )
1101
1154
 
1102
1155
  def new_group(
1103
1156
  self,
@@ -1117,13 +1170,13 @@ class Ctx:
1117
1170
  def create_group(
1118
1171
  self,
1119
1172
  name: Optional[str] = None,
1120
- meta_id: Optional[int] = None
1173
+ meta_id: Optional[Union[int, list]] = None
1121
1174
  ):
1122
1175
  """
1123
1176
  Make directory
1124
1177
 
1125
1178
  :param name: name of directory
1126
- :param meta_id: int
1179
+ :param meta_id: int or list of int
1127
1180
  """
1128
1181
  if name is None:
1129
1182
  self.window.update_status(
@@ -1133,53 +1186,69 @@ class Ctx:
1133
1186
  group = self.window.core.ctx.make_group(name)
1134
1187
  id = self.window.core.ctx.insert_group(group)
1135
1188
  if id is not None:
1136
- if meta_id is not None:
1137
- self.move_to_group(meta_id, id, update=False)
1138
- self.update()
1139
- self.window.update_status(
1140
- "Group '{}' created.".format(name)
1141
- )
1142
- self.window.ui.dialog['create'].close()
1143
- # self.select_group(id)
1144
- self.group_id = id
1189
+ ids = meta_id if isinstance(meta_id, list) else [meta_id] if meta_id is not None else []
1190
+ for meta_id in ids:
1191
+ if meta_id is not None:
1192
+ self.move_to_group(meta_id, id, update=False)
1193
+
1194
+ self.update()
1195
+ self.window.update_status(
1196
+ "Group '{}' created.".format(name)
1197
+ )
1198
+ self.window.ui.dialog['create'].close()
1199
+ # self.select_group(id)
1200
+ self.group_id = id
1145
1201
 
1146
1202
  def rename_group(
1147
1203
  self,
1148
- id: int,
1204
+ id: Union[int, list],
1149
1205
  force: bool = False
1150
1206
  ):
1151
1207
  """
1152
1208
  Rename group
1153
1209
 
1154
- :param id: group ID
1210
+ :param id: group ID or list of IDs
1155
1211
  :param force: force rename
1156
1212
  """
1213
+ ids = id if isinstance(id, list) else [id]
1157
1214
  if not force:
1158
- group = self.window.core.ctx.get_group_by_id(id)
1159
- if group is None:
1215
+ is_group = False
1216
+ name = ""
1217
+ for tmp_id in ids:
1218
+ group = self.window.core.ctx.get_group_by_id(tmp_id)
1219
+ if group is not None:
1220
+ is_group = True
1221
+ name = group.name
1222
+ break
1223
+ if not is_group:
1160
1224
  return
1161
1225
  self.window.ui.dialog['rename'].id = 'ctx.group'
1162
- self.window.ui.dialog['rename'].input.setText(group.name)
1226
+ self.window.ui.dialog['rename'].input.setText(name)
1163
1227
  self.window.ui.dialog['rename'].current = id
1164
1228
  self.window.ui.dialog['rename'].show()
1165
1229
 
1166
1230
  def update_group_name(
1167
1231
  self,
1168
- id: int,
1232
+ id: Union[int, list],
1169
1233
  name: str,
1170
1234
  close: bool = True
1171
1235
  ):
1172
1236
  """
1173
1237
  Update group name
1174
1238
 
1175
- :param id: group ID
1239
+ :param id: group ID or list of IDs
1176
1240
  :param name: group name
1177
1241
  :param close: close rename dialog
1178
1242
  """
1179
- group = self.window.core.ctx.get_group_by_id(id)
1180
- if group is not None:
1181
- group.name = name
1182
- self.window.core.ctx.update_group(group)
1243
+ updated = False
1244
+ ids = id if isinstance(id, list) else [id]
1245
+ for id in ids:
1246
+ group = self.window.core.ctx.get_group_by_id(id)
1247
+ if group is not None:
1248
+ group.name = name
1249
+ self.window.core.ctx.update_group(group)
1250
+ updated = True
1251
+ if updated:
1183
1252
  if close:
1184
1253
  self.window.ui.dialog['rename'].close()
1185
1254
  self.update_and_restore()
@@ -1207,17 +1276,18 @@ class Ctx:
1207
1276
  nodes = self.window.ui.nodes
1208
1277
  nodes['ctx.list'].unlocked = True
1209
1278
  nodes['ctx.list'].setCurrentIndex(index)
1279
+ nodes['ctx.list'].force_scroll_to_current()
1210
1280
  nodes['ctx.list'].unlocked = False
1211
1281
 
1212
1282
  def delete_group(
1213
1283
  self,
1214
- id: int,
1284
+ id: Union[int, list],
1215
1285
  force: bool = False
1216
1286
  ):
1217
1287
  """
1218
1288
  Delete group only
1219
1289
 
1220
- :param id: group ID
1290
+ :param id: group ID or list of IDs
1221
1291
  :param force: force delete
1222
1292
  """
1223
1293
  if not force:
@@ -1227,22 +1297,28 @@ class Ctx:
1227
1297
  msg=trans('confirm.ctx.delete')
1228
1298
  )
1229
1299
  return
1230
- group = self.window.core.ctx.get_group_by_id(id)
1231
- if group is not None:
1232
- self.window.core.ctx.remove_group(group, all=False)
1233
- if self.group_id == id:
1234
- self.group_id = None
1300
+ updated = False
1301
+ ids = id if isinstance(id, list) else [id]
1302
+ for id in ids:
1303
+ group = self.window.core.ctx.get_group_by_id(id)
1304
+ if group is not None:
1305
+ self.window.core.ctx.remove_group(group, all=False)
1306
+ if self.group_id == id:
1307
+ self.group_id = None
1308
+ updated = True
1309
+
1310
+ if updated:
1235
1311
  self.update_and_restore()
1236
1312
 
1237
1313
  def delete_group_all(
1238
1314
  self,
1239
- id: int,
1315
+ id: Union[int, list],
1240
1316
  force: bool = False
1241
1317
  ):
1242
1318
  """
1243
1319
  Delete group with all items
1244
1320
 
1245
- :param id: group ID
1321
+ :param id: group ID or list of IDs
1246
1322
  :param force: force delete
1247
1323
  """
1248
1324
  if not force:
@@ -1252,11 +1328,16 @@ class Ctx:
1252
1328
  msg=trans('confirm.ctx.delete.all')
1253
1329
  )
1254
1330
  return
1255
- group = self.window.core.ctx.get_group_by_id(id)
1256
- if group is not None:
1257
- self.window.core.ctx.remove_group(group, all=True)
1258
- if self.group_id == id:
1259
- self.group_id = None
1331
+ updated = False
1332
+ ids = id if isinstance(id, list) else [id]
1333
+ for id in ids:
1334
+ group = self.window.core.ctx.get_group_by_id(id)
1335
+ if group is not None:
1336
+ self.window.core.ctx.remove_group(group, all=True)
1337
+ if self.group_id == id:
1338
+ self.group_id = None
1339
+ updated = True
1340
+ if updated:
1260
1341
  self.update_and_restore()
1261
1342
 
1262
1343
  def prepare_summary(self, ctx: CtxItem) -> bool: