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