pygpt-net 2.4.42__py3-none-any.whl → 2.4.45__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 (71) hide show
  1. CHANGELOG.md +15 -0
  2. README.md +21 -2
  3. pygpt_net/CHANGELOG.txt +15 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/controller/attachment.py +31 -3
  6. pygpt_net/controller/chat/attachment.py +37 -36
  7. pygpt_net/controller/config/placeholder.py +6 -4
  8. pygpt_net/controller/idx/common.py +7 -3
  9. pygpt_net/controller/lang/mapping.py +32 -9
  10. pygpt_net/core/attachments/__init__.py +7 -2
  11. pygpt_net/core/attachments/context.py +52 -34
  12. pygpt_net/core/db/__init__.py +2 -1
  13. pygpt_net/core/debug/attachments.py +1 -0
  14. pygpt_net/core/idx/__init__.py +8 -3
  15. pygpt_net/core/idx/indexing.py +24 -7
  16. pygpt_net/core/idx/ui/__init__.py +22 -0
  17. pygpt_net/core/idx/ui/loaders.py +217 -0
  18. pygpt_net/data/config/config.json +4 -4
  19. pygpt_net/data/config/models.json +3 -3
  20. pygpt_net/data/config/modes.json +3 -3
  21. pygpt_net/data/config/settings.json +5 -5
  22. pygpt_net/data/css/style.css +1 -0
  23. pygpt_net/data/locale/locale.de.ini +4 -4
  24. pygpt_net/data/locale/locale.en.ini +11 -9
  25. pygpt_net/data/locale/locale.es.ini +4 -4
  26. pygpt_net/data/locale/locale.fr.ini +4 -4
  27. pygpt_net/data/locale/locale.it.ini +4 -4
  28. pygpt_net/data/locale/locale.pl.ini +4 -4
  29. pygpt_net/data/locale/locale.uk.ini +4 -4
  30. pygpt_net/data/locale/locale.zh.ini +4 -4
  31. pygpt_net/data/locale/plugin.mailer.en.ini +5 -5
  32. pygpt_net/item/attachment.py +5 -1
  33. pygpt_net/item/ctx.py +99 -2
  34. pygpt_net/migrations/Version20241215110000.py +25 -0
  35. pygpt_net/migrations/__init__.py +3 -1
  36. pygpt_net/plugin/cmd_files/__init__.py +3 -2
  37. pygpt_net/provider/core/attachment/json_file.py +4 -1
  38. pygpt_net/provider/core/config/patch.py +12 -0
  39. pygpt_net/provider/core/ctx/db_sqlite/storage.py +50 -7
  40. pygpt_net/provider/core/ctx/db_sqlite/utils.py +29 -5
  41. pygpt_net/provider/loaders/base.py +14 -0
  42. pygpt_net/provider/loaders/hub/google/gmail.py +2 -2
  43. pygpt_net/provider/loaders/hub/yt/base.py +5 -0
  44. pygpt_net/provider/loaders/web_database.py +13 -5
  45. pygpt_net/provider/loaders/web_github_issues.py +18 -1
  46. pygpt_net/provider/loaders/web_github_repo.py +10 -0
  47. pygpt_net/provider/loaders/web_google_calendar.py +9 -1
  48. pygpt_net/provider/loaders/web_google_docs.py +6 -1
  49. pygpt_net/provider/loaders/web_google_drive.py +10 -1
  50. pygpt_net/provider/loaders/web_google_gmail.py +5 -3
  51. pygpt_net/provider/loaders/web_google_keep.py +5 -1
  52. pygpt_net/provider/loaders/web_google_sheets.py +5 -1
  53. pygpt_net/provider/loaders/web_microsoft_onedrive.py +15 -1
  54. pygpt_net/provider/loaders/web_page.py +4 -2
  55. pygpt_net/provider/loaders/web_rss.py +3 -1
  56. pygpt_net/provider/loaders/web_sitemap.py +9 -3
  57. pygpt_net/provider/loaders/web_twitter.py +4 -2
  58. pygpt_net/provider/loaders/web_yt.py +17 -2
  59. pygpt_net/provider/vector_stores/ctx_attachment.py +1 -1
  60. pygpt_net/tools/indexer/__init__.py +8 -40
  61. pygpt_net/tools/indexer/ui/web.py +33 -80
  62. pygpt_net/ui/layout/ctx/ctx_list.py +86 -18
  63. pygpt_net/ui/widget/dialog/url.py +162 -14
  64. pygpt_net/ui/widget/element/group.py +15 -2
  65. pygpt_net/ui/widget/lists/context.py +23 -9
  66. pygpt_net/utils.py +1 -1
  67. {pygpt_net-2.4.42.dist-info → pygpt_net-2.4.45.dist-info}/METADATA +22 -3
  68. {pygpt_net-2.4.42.dist-info → pygpt_net-2.4.45.dist-info}/RECORD +71 -68
  69. {pygpt_net-2.4.42.dist-info → pygpt_net-2.4.45.dist-info}/LICENSE +0 -0
  70. {pygpt_net-2.4.42.dist-info → pygpt_net-2.4.45.dist-info}/WHEEL +0 -0
  71. {pygpt_net-2.4.42.dist-info → pygpt_net-2.4.45.dist-info}/entry_points.txt +0 -0
@@ -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: 2024.04.17 01:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -30,18 +30,26 @@ class Loader(BaseLoader):
30
30
  "args": {
31
31
  "folder_id": {
32
32
  "type": "str",
33
+ "label": "Folder ID",
33
34
  },
34
35
  "file_ids": {
35
36
  "type": "list",
37
+ "label": "File IDs",
38
+ "description": "List of file ids, separated by comma (,)",
36
39
  },
37
40
  "mime_types": {
38
41
  "type": "list",
42
+ "label": "Mime Types",
43
+ "description": "List of mime types, separated by comma (,)",
39
44
  },
40
45
  "folder_path": {
41
46
  "type": "str",
47
+ "label": "Folder Path",
42
48
  },
43
49
  "file_paths": {
44
50
  "type": "list",
51
+ "label": "File Paths",
52
+ "description": "List of file paths, separated by comma (,)",
45
53
  },
46
54
  },
47
55
  }
@@ -98,10 +106,14 @@ class Loader(BaseLoader):
98
106
  if "file_ids" in kwargs and kwargs.get("file_ids"):
99
107
  if isinstance(kwargs.get("file_ids"), list):
100
108
  args["file_ids"] = kwargs.get("file_ids") # list of file ids
109
+ elif isinstance(kwargs.get("file_ids"), str):
110
+ args["file_ids"] = self.explode(kwargs.get("file_ids"))
101
111
 
102
112
  if "mime_types" in kwargs and kwargs.get("mime_types"):
103
113
  if isinstance(kwargs.get("mime_types"), list):
104
114
  args["mime_types"] = kwargs.get("mime_types") # list of mime types
115
+ elif isinstance(kwargs.get("mime_types"), str):
116
+ args["mime_types"] = self.explode(kwargs.get("mime_types"))
105
117
 
106
118
  if "folder_path" in kwargs and kwargs.get("folder_path"):
107
119
  if isinstance(kwargs.get("folder_path"), str):
@@ -110,4 +122,6 @@ class Loader(BaseLoader):
110
122
  if "file_paths" in kwargs and kwargs.get("file_paths"):
111
123
  if isinstance(kwargs.get("file_paths"), list):
112
124
  args["file_paths"] = kwargs.get("file_paths") # list of file paths
125
+ elif isinstance(kwargs.get("file_paths"), str):
126
+ args["file_paths"] = self.explode(kwargs.get("file_paths"))
113
127
  return args
@@ -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: 2024.03.12 06:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from llama_index.core.readers.base import BaseReader
@@ -19,7 +19,7 @@ class Loader(BaseLoader):
19
19
  def __init__(self, *args, **kwargs):
20
20
  super().__init__(*args, **kwargs)
21
21
  self.id = "webpage"
22
- self.name = "Webpages"
22
+ self.name = "Webpage"
23
23
  self.type = ["web"]
24
24
  self.instructions = [
25
25
  {
@@ -28,6 +28,8 @@ class Loader(BaseLoader):
28
28
  "args": {
29
29
  "url": {
30
30
  "type": "str",
31
+ "label": "URL",
32
+ "description": "URL of the webpage to index, e.g. https://www.example.com",
31
33
  },
32
34
  },
33
35
  }
@@ -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: 2024.03.12 06:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from llama_index.core.readers.base import BaseReader
@@ -28,6 +28,8 @@ class Loader(BaseLoader):
28
28
  "args": {
29
29
  "url": {
30
30
  "type": "str",
31
+ "label": "URL",
32
+ "description": "URL to RSS feed, e.g. https://example.com/feed.xml",
31
33
  },
32
34
  },
33
35
  }
@@ -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: 2024.04.17 01:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from llama_index.core.readers.base import BaseReader
@@ -23,11 +23,13 @@ class Loader(BaseLoader):
23
23
  self.type = ["web"]
24
24
  self.instructions = [
25
25
  {
26
- "rss": {
27
- "description": "read sitemap XML from URL",
26
+ "sitemap": {
27
+ "description": "read all web pages from sitemap.xml",
28
28
  "args": {
29
29
  "url": {
30
30
  "type": "str",
31
+ "label": "URL",
32
+ "description": "URL to sitemap XML, e.g. https://example.com/sitemap.xml, all pages will be read",
31
33
  },
32
34
  },
33
35
  }
@@ -41,6 +43,10 @@ class Loader(BaseLoader):
41
43
  "html_to_text": "bool",
42
44
  "limit": "int",
43
45
  }
46
+ self.init_args_desc = {
47
+ "html_to_text": "Whether to convert HTML to text",
48
+ "limit": "Maximum number of concurrent requests",
49
+ }
44
50
 
45
51
  def get(self) -> BaseReader:
46
52
  """
@@ -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: 2024.04.17 01:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -30,9 +30,11 @@ class Loader(BaseLoader):
30
30
  "args": {
31
31
  "users": {
32
32
  "type": "list",
33
+ "label": "Twitter/X usernames",
33
34
  },
34
35
  "max_tweets": {
35
36
  "type": "int",
37
+ "label": "Max tweets",
36
38
  },
37
39
  },
38
40
  }
@@ -80,7 +82,7 @@ class Loader(BaseLoader):
80
82
  if isinstance(kwargs.get("users"), list):
81
83
  args["twitterhandles"] = kwargs.get("users") # usernames
82
84
  elif isinstance(kwargs.get("users"), str):
83
- args["twitterhandles"] = [kwargs.get("users")]
85
+ args["twitterhandles"] = self.explode(kwargs.get("users"))
84
86
 
85
87
  if "max_tweets" in kwargs and kwargs.get("max_tweets"):
86
88
  if isinstance(kwargs.get("max_tweets"), int):
@@ -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: 2024.11.26 04:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from llama_index.core.readers.base import BaseReader
@@ -28,11 +28,25 @@ class Loader(BaseLoader):
28
28
  "args": {
29
29
  "url": {
30
30
  "type": "str",
31
+ "label": "Youtube URL",
32
+ "description": "URL of the YouTube video, e.g. https://www.youtube.com/watch?v=CRRlbK5w8AE",
31
33
  },
32
34
  },
33
35
  }
34
36
  }
35
37
  ]
38
+ self.init_args = {
39
+ "languages": ["en"],
40
+ }
41
+ self.init_args_types = {
42
+ "languages": "list",
43
+ }
44
+ self.init_args_labels = {
45
+ "languages": "Languages",
46
+ }
47
+ self.init_args_desc = {
48
+ "languages": "List of languages to extract from the video, separated by comma (,), e.g. 'en,de,fr'. Default is 'en'",
49
+ }
36
50
 
37
51
  def get(self) -> BaseReader:
38
52
  """
@@ -40,7 +54,8 @@ class Loader(BaseLoader):
40
54
 
41
55
  :return: Data reader instance
42
56
  """
43
- return YoutubeTranscriptReader()
57
+ args = self.get_args()
58
+ return YoutubeTranscriptReader(**args)
44
59
 
45
60
  def prepare_args(self, **kwargs) -> dict:
46
61
  """
@@ -50,7 +50,7 @@ class CtxAttachmentProvider(BaseStore):
50
50
 
51
51
  :return: True if exists
52
52
  """
53
- path = self.get_path()
53
+ path = self.get_path("")
54
54
  if os.path.exists(path):
55
55
  store = os.path.join(path, "docstore.json")
56
56
  if os.path.exists(store):
@@ -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: 2024.12.14 22:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
@@ -311,48 +311,16 @@ class IndexerTool(BaseTool):
311
311
 
312
312
  :param force: force indexing
313
313
  """
314
- input_params = {}
315
- input_config = {}
316
- is_replace = self.window.ui.nodes["tool.indexer.web.options.replace"].isChecked()
317
- loader = self.window.ui.nodes["tool.indexer.web.loader"].get_value()
318
- if not loader:
314
+ result, loader, input_params, input_config = self.window.core.idx.ui.loaders.handle_options(
315
+ self.window.ui.nodes["tool.indexer.web.loader"],
316
+ "tool.indexer.web.loader.option",
317
+ "tool.indexer.web.loader.config",
318
+ )
319
+ if not result:
319
320
  self.window.ui.dialogs.alert(trans("tool.indexer.alert.no_loader"))
320
321
  return
321
- loaders = self.window.core.idx.indexing.get_external_instructions()
322
- if loader in loaders:
323
- params = loaders[loader]
324
- for k in params["args"]:
325
- key_path = "tool.indexer.web.loader.option." + loader + "." + k
326
- if key_path in self.window.ui.nodes:
327
- input_params[k] = self.window.ui.nodes[key_path].text()
328
322
 
329
- loaders = self.window.core.idx.indexing.get_external_config()
330
- if loader in loaders:
331
- params = loaders[loader]
332
- for k in params:
333
- key_path = "tool.indexer.web.loader.config." + loader + "." + k
334
- type = params[k]["type"]
335
- if key_path in self.window.ui.nodes:
336
- tmp_value = self.window.ui.nodes[key_path].text()
337
- try:
338
- if tmp_value:
339
- if type == "int":
340
- tmp_value = int(tmp_value)
341
- elif type == "float":
342
- tmp_value = float(tmp_value)
343
- elif type == "bool":
344
- if tmp_value.lower() in ["true", "1"]:
345
- tmp_value = True
346
- else:
347
- tmp_value = False
348
- elif type == "list":
349
- tmp_value = tmp_value.split(",")
350
- elif type == "dict":
351
- tmp_value = json.loads(tmp_value)
352
- input_config[k] = tmp_value
353
- except Exception as e:
354
- self.window.core.debug.log(e)
355
- self.window.ui.dialogs.alert(e)
323
+ is_replace = self.window.ui.nodes["tool.indexer.web.options.replace"].isChecked()
356
324
  if not force:
357
325
  self.window.ui.dialogs.confirm(
358
326
  type="idx.tool.index",
@@ -6,16 +6,18 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.08.19 20:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
13
13
 
14
14
  from PySide6.QtCore import Qt
15
+ from PySide6.QtGui import QIcon
15
16
  from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget, QCheckBox, QHBoxLayout, QScrollArea, \
16
17
  QSizePolicy
17
18
 
18
- from pygpt_net.ui.widget.element.labels import HelpLabel, UrlLabel
19
+ from pygpt_net.ui.widget.element.group import QVLine, QHLine
20
+ from pygpt_net.ui.widget.element.labels import HelpLabel, UrlLabel, IconLabel
19
21
  from pygpt_net.ui.widget.option.combo import OptionCombo
20
22
  from pygpt_net.ui.widget.option.input import OptionInput
21
23
  from pygpt_net.utils import trans
@@ -33,15 +35,17 @@ class WebTab:
33
35
  self.params_widget = None
34
36
 
35
37
  def setup(self):
36
- """
37
- Setup tab widget
38
- """
38
+ """Setup tab widget"""
39
39
  # get loaders list
40
40
  loaders = self.window.controller.config.placeholder.apply_by_id("llama_index_loaders_web")
41
41
  loaders_list = []
42
42
  for loader in loaders:
43
- key = list(loader.keys())[0]
44
- loaders_list.append(key.replace("web_", ""))
43
+ k = list(loader.keys())[0]
44
+ key = k.replace("web_", "")
45
+ value = loader[k]
46
+ loaders_list.append({
47
+ key: value,
48
+ })
45
49
 
46
50
  self.window.ui.nodes["tool.indexer.web.loader"] = OptionCombo(
47
51
  self.window,
@@ -55,14 +59,25 @@ class WebTab:
55
59
  )
56
60
 
57
61
  self.window.ui.nodes["tool.indexer.web.loader"].layout.setContentsMargins(0, 0, 0, 0)
58
- self.window.ui.nodes["tool.indexer.web.loader.label"] = QLabel(trans("tool.indexer.tab.web.loader"))
62
+ self.window.ui.nodes["tool.indexer.web.loader.label"] = HelpLabel(trans("tool.indexer.tab.web.loader"))
59
63
  self.window.ui.add_hook("update.tool.indexer.web.loader", self.hook_loader_change)
60
64
 
61
65
  self.window.ui.nodes["tool.indexer.web.options.label"] = HelpLabel(trans("tool.indexer.tab.web.source"))
62
- self.window.ui.nodes["tool.indexer.web.config.label"] = HelpLabel(trans("tool.indexer.tab.web.cfg"))
66
+
67
+ config_label = HelpLabel(trans("tool.indexer.tab.web.cfg"))
68
+ config_label.setWordWrap(False)
69
+
70
+ config_label_layout = QHBoxLayout()
71
+ config_label_layout.addWidget(IconLabel(QIcon(":/icons/settings_filled.svg")))
72
+ config_label_layout.addWidget(config_label)
73
+ config_label_layout.setAlignment(Qt.AlignLeft)
74
+
75
+ self.window.ui.nodes["tool.indexer.web.config.label"] = QWidget()
76
+ self.window.ui.nodes["tool.indexer.web.config.label"].setLayout(config_label_layout)
77
+
63
78
  self.window.ui.nodes["tool.indexer.web.config.help"] = UrlLabel(
64
79
  trans("tool.indexer.tab.web.help"),
65
- "https://pygpt.readthedocs.io/en/latest/modes.html#chat-with-files-llama-index")
80
+ "https://pygpt.readthedocs.io/en/latest/configuration.html#data-loaders")
66
81
 
67
82
  # --------------------------------------------------
68
83
 
@@ -78,7 +93,7 @@ class WebTab:
78
93
 
79
94
  # params
80
95
  params_layout.addWidget(self.window.ui.nodes["tool.indexer.web.options.label"])
81
- inputs, groups = self.setup_loader_options()
96
+ inputs, groups = self.window.core.idx.ui.loaders.setup_loader_options()
82
97
  for loader in inputs:
83
98
  for k in inputs[loader]:
84
99
  self.window.ui.nodes["tool.indexer.web.loader.option." + loader + "." + k] = inputs[loader][k]
@@ -87,9 +102,12 @@ class WebTab:
87
102
  params_layout.addWidget(self.window.ui.nodes["tool.indexer.web.loader.option_group"][loader])
88
103
  self.window.ui.nodes["tool.indexer.web.loader.option_group"][loader].hide() # hide on start
89
104
 
105
+ # separator
106
+ params_layout.addWidget(QHLine())
107
+
90
108
  # config
91
109
  params_layout.addWidget(self.window.ui.nodes["tool.indexer.web.config.label"])
92
- inputs, groups = self.setup_loader_config()
110
+ inputs, groups = self.window.core.idx.ui.loaders.setup_loader_config()
93
111
  for loader in inputs:
94
112
  for k in inputs[loader]:
95
113
  self.window.ui.nodes["tool.indexer.web.loader.config." + loader + "." + k] = inputs[loader][k]
@@ -99,6 +117,7 @@ class WebTab:
99
117
  self.window.ui.nodes["tool.indexer.web.loader.config_group"][loader].hide() # hide on start
100
118
  params_layout.addWidget(self.window.ui.nodes["tool.indexer.web.config.help"], alignment=Qt.AlignCenter)
101
119
 
120
+ # stretch
102
121
  params_layout.addStretch(1)
103
122
 
104
123
  self.params_widget = QWidget()
@@ -117,9 +136,10 @@ class WebTab:
117
136
 
118
137
  self.window.ui.nodes["tool.indexer.provider"] = HelpLabel(self.window.core.config.get("llama.idx.storage"))
119
138
 
120
- loader_layout = QHBoxLayout()
139
+ loader_layout = QVBoxLayout()
121
140
  loader_layout.addWidget(self.window.ui.nodes["tool.indexer.web.loader.label"])
122
141
  loader_layout.addWidget(self.window.ui.nodes["tool.indexer.web.loader"])
142
+ loader_layout.setContentsMargins(0, 10, 0, 0)
123
143
 
124
144
  options_layout = QVBoxLayout()
125
145
  options_layout.addWidget(self.window.ui.nodes["tool.indexer.web.options.replace"])
@@ -177,70 +197,3 @@ class WebTab:
177
197
 
178
198
  self.params_widget.adjustSize()
179
199
  self.params_scroll.update()
180
-
181
- def setup_loader_options(self):
182
- """
183
- Setup loader options
184
- """
185
- inputs = {}
186
- groups = {}
187
- loaders = self.window.core.idx.indexing.get_external_instructions()
188
- for loader in loaders:
189
- params = loaders[loader]
190
- inputs[loader] = {}
191
- group = QVBoxLayout()
192
- for k in params["args"]:
193
- widget = OptionInput(self.window, "tool.indexer", f"web.loader.{loader}.option.{k}", {
194
- "label": k,
195
- "value": "",
196
- })
197
- widget.setPlaceholderText(params["args"][k]["type"])
198
- inputs[loader][k] = widget
199
- row = QHBoxLayout() # cols
200
- row.addWidget(QLabel(k))
201
- row.addWidget(widget)
202
- group.addLayout(row)
203
- group_widget = QWidget()
204
- group_widget.setLayout(group)
205
- groups[loader] = group_widget
206
-
207
- return inputs, groups
208
-
209
- def setup_loader_config(self):
210
- """
211
- Setup loader config
212
- """
213
- inputs = {}
214
- groups = {}
215
- loaders = self.window.core.idx.indexing.get_external_config()
216
- for loader in loaders:
217
- params = loaders[loader]
218
- inputs[loader] = {}
219
- group = QVBoxLayout()
220
- for k in params:
221
- widget = OptionInput(self.window, "tool.indexer", f"web.loader.{loader}.config.{k}", {
222
- "label": k,
223
- "value": params[k]["value"],
224
- })
225
- try:
226
- if params[k]["value"] is not None:
227
- if params[k]["type"] == "list" and isinstance(params[k]["value"], list):
228
- widget.setText(", ".join(params[k]["value"]))
229
- elif params[k]["type"] == "dict" and isinstance(params[k]["value"], dict):
230
- widget.setText(json.dumps(params[k]["value"]))
231
- else:
232
- widget.setText(str(params[k]["value"]))
233
- except Exception as e:
234
- self.window.core.debug.log(e)
235
-
236
- widget.setPlaceholderText(params[k]["type"])
237
- inputs[loader][k] = widget
238
- row = QHBoxLayout() # cols
239
- row.addWidget(QLabel(k))
240
- row.addWidget(widget)
241
- group.addLayout(row)
242
- group_widget = QWidget()
243
- group_widget.setLayout(group)
244
- groups[loader] = group_widget
245
-
246
- return inputs, groups
@@ -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: 2024.12.12 04:00:00 #
9
+ # Updated Date: 2024.12.16 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6 import QtCore
@@ -81,9 +81,11 @@ class CtxList:
81
81
  self.window.ui.models[id].removeRows(0, self.window.ui.models[id].rowCount())
82
82
 
83
83
  if self.window.core.config.get("ctx.records.folders.top"):
84
+ self.update_items_pinned(id, data)
84
85
  self.update_groups(id, data)
85
86
  self.update_items(id, data)
86
87
  else:
88
+ self.update_items_pinned(id, data)
87
89
  self.update_items(id, data)
88
90
  self.update_groups(id, data)
89
91
 
@@ -96,13 +98,43 @@ class CtxList:
96
98
  """
97
99
  i = 0
98
100
  last_dt_str = None
101
+ separators = self.window.core.config.get("ctx.records.separators")
102
+ pinned_separators = self.window.core.config.get("ctx.records.pinned.separators")
99
103
  for meta_id in data:
100
104
  if data[meta_id].group_id is None or data[meta_id].group_id == 0:
105
+ if data[meta_id].important:
106
+ continue
101
107
  item = self.build_item(meta_id, data[meta_id], is_group=False)
102
- if self.window.core.config.get("ctx.records.separators"):
103
- if not item.isPinned or self.window.core.config.get("ctx.records.pinned.separators"):
108
+ if separators:
109
+ if not item.isPinned or pinned_separators:
104
110
  if i == 0 or last_dt_str != item.dt:
105
- section = self.build_date_section(item.dt)
111
+ section = self.build_date_section(item.dt, group=False)
112
+ if section:
113
+ self.window.ui.models[id].appendRow(section)
114
+ last_dt_str = item.dt
115
+ self.window.ui.models[id].appendRow(item)
116
+ i += 1
117
+
118
+ def update_items_pinned(self, id, data):
119
+ """
120
+ Update items pinned
121
+
122
+ :param id: ID of the list
123
+ :param data: Data to update
124
+ """
125
+ i = 0
126
+ last_dt_str = None
127
+ separators = self.window.core.config.get("ctx.records.separators")
128
+ pinned_separators = self.window.core.config.get("ctx.records.pinned.separators")
129
+ for meta_id in data:
130
+ if data[meta_id].group_id is None or data[meta_id].group_id == 0:
131
+ if not data[meta_id].important:
132
+ continue
133
+ item = self.build_item(meta_id, data[meta_id], is_group=False)
134
+ if separators:
135
+ if pinned_separators:
136
+ if i == 0 or last_dt_str != item.dt:
137
+ section = self.build_date_section(item.dt, group=False)
106
138
  if section:
107
139
  self.window.ui.models[id].appendRow(section)
108
140
  last_dt_str = item.dt
@@ -116,21 +148,37 @@ class CtxList:
116
148
  :param id: ID of the list
117
149
  :param data: Data to update
118
150
  """
119
- # get groups
120
- groups = self.window.core.ctx.get_groups()
121
-
151
+ groups = self.window.core.ctx.get_groups() # get groups
122
152
  for group_id in groups:
123
153
  last_dt_str = None
124
154
  group = groups[group_id]
125
155
  c = self.count_in_group(group.id, data)
126
- if c == 0 and self.window.core.ctx.get_search_string() is not None and self.window.core.ctx.get_search_string() != "":
156
+ if (c == 0 and self.window.core.ctx.get_search_string() is not None
157
+ and self.window.core.ctx.get_search_string() != ""):
127
158
  continue # skip empty groups when searching
128
159
 
129
160
  suffix = ""
130
161
  if c > 0:
131
162
  suffix = " (" + str(c) + ")"
163
+ is_attachment = group.has_additional_ctx()
132
164
  group_name = group.name + suffix
133
165
  group_item = GroupItem(QIcon(":/icons/folder_filled.svg"), group_name, group.id)
166
+ group_item.hasAttachments = group.has_additional_ctx()
167
+ custom_data = {
168
+ "is_group": True,
169
+ "is_attachment": is_attachment,
170
+ }
171
+ if is_attachment:
172
+ files = group.get_attachment_names()
173
+ num = len(files)
174
+ files_str = ", ".join(files)
175
+ if len(files_str) > 40:
176
+ files_str = files_str[:40] + '...'
177
+ tooltip_str = trans("attachments.ctx.tooltip.list").format(num=num) + ": " + files_str
178
+ group_item.setToolTip(tooltip_str)
179
+
180
+ group_item.setData(custom_data, QtCore.Qt.ItemDataRole.UserRole)
181
+
134
182
  i = 0
135
183
  for meta_id in data:
136
184
  if data[meta_id].group_id != group.id:
@@ -139,7 +187,7 @@ class CtxList:
139
187
  if self.window.core.config.get("ctx.records.groups.separators"):
140
188
  if not item.isPinned or self.window.core.config.get("ctx.records.pinned.separators"):
141
189
  if i == 0 or last_dt_str != item.dt:
142
- section = self.build_date_section(item.dt)
190
+ section = self.build_date_section(item.dt, group=True)
143
191
  if section:
144
192
  group_item.appendRow(section)
145
193
  last_dt_str = item.dt
@@ -178,6 +226,17 @@ class CtxList:
178
226
  :return: Item
179
227
  """
180
228
  append_dt = True
229
+ is_important = False
230
+ is_attachment = False
231
+ in_group = False
232
+ label = data.label
233
+ if data.important:
234
+ is_important = True
235
+ if data.has_additional_ctx():
236
+ is_attachment = True
237
+ if data.group:
238
+ in_group = True
239
+
181
240
  if is_group:
182
241
  if self.window.core.config.get("ctx.records.groups.separators"):
183
242
  append_dt = False
@@ -206,35 +265,44 @@ class CtxList:
206
265
  mode_str,
207
266
  id,
208
267
  )
268
+
269
+ # append attachments to tooltip
270
+ if is_attachment:
271
+ files = data.get_attachment_names()
272
+ num = len(files)
273
+ files_str = ", ".join(files)
274
+ if len(files_str) > 40:
275
+ files_str = files_str[:40] + '...'
276
+ tooltip_str = trans("attachments.ctx.tooltip.list").format(num=num) + ": " + files_str
277
+ tooltip_text += "\n" + tooltip_str
278
+
209
279
  item = Item(name, id)
210
280
  item.id = id
211
281
  item.dt = dt
212
282
  item.isPinned = data.important
213
283
  item.setData(tooltip_text, QtCore.Qt.ToolTipRole)
214
- is_important = False
215
- is_attachment = False
216
- label = data.label
217
- if data.important:
218
- is_important = True
219
- if data.additional_ctx and len(data.additional_ctx) > 0:
220
- is_attachment = True
284
+
221
285
  custom_data = {
222
286
  "label": label,
223
287
  "is_important": is_important,
224
288
  "is_attachment": is_attachment,
289
+ "in_group": in_group,
225
290
  }
226
291
  item.setData(custom_data, QtCore.Qt.ItemDataRole.UserRole)
227
292
  item.setData(name)
228
293
  return item
229
294
 
230
- def build_date_section(self, dt: str) -> SectionItem:
295
+ def build_date_section(self, dt: str, group: bool = False) -> SectionItem:
231
296
  """
232
297
  Build date section
233
298
 
234
299
  :param dt: date section string
300
+ :param group: is group
235
301
  :return: SectionItem
236
302
  """
237
- return SectionItem(dt)
303
+ item = SectionItem(dt, group=group)
304
+ # item.setToolTip(dt)
305
+ return item
238
306
 
239
307
  def convert_date(self, timestamp: int) -> str:
240
308
  """