pygpt-net 2.7.4__py3-none-any.whl → 2.7.5__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 (133) hide show
  1. pygpt_net/CHANGELOG.txt +7 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app_core.py +4 -2
  4. pygpt_net/controller/__init__.py +5 -1
  5. pygpt_net/controller/assistant/assistant.py +1 -4
  6. pygpt_net/controller/assistant/batch.py +5 -504
  7. pygpt_net/controller/assistant/editor.py +5 -5
  8. pygpt_net/controller/assistant/files.py +16 -16
  9. pygpt_net/controller/chat/handler/google_stream.py +307 -1
  10. pygpt_net/controller/chat/handler/worker.py +8 -1
  11. pygpt_net/controller/chat/image.py +2 -2
  12. pygpt_net/controller/dialogs/confirm.py +73 -101
  13. pygpt_net/controller/lang/mapping.py +9 -9
  14. pygpt_net/controller/painter/capture.py +50 -1
  15. pygpt_net/controller/presets/presets.py +2 -1
  16. pygpt_net/controller/remote_store/__init__.py +12 -0
  17. pygpt_net/{provider/core/assistant_file/db_sqlite → controller/remote_store/google}/__init__.py +2 -2
  18. pygpt_net/controller/remote_store/google/batch.py +402 -0
  19. pygpt_net/controller/remote_store/google/store.py +615 -0
  20. pygpt_net/controller/remote_store/openai/__init__.py +12 -0
  21. pygpt_net/controller/remote_store/openai/batch.py +524 -0
  22. pygpt_net/controller/{assistant → remote_store/openai}/store.py +63 -60
  23. pygpt_net/controller/remote_store/remote_store.py +35 -0
  24. pygpt_net/controller/ui/ui.py +20 -1
  25. pygpt_net/core/assistants/assistants.py +3 -15
  26. pygpt_net/core/db/database.py +5 -3
  27. pygpt_net/core/locale/placeholder.py +35 -0
  28. pygpt_net/core/remote_store/__init__.py +12 -0
  29. pygpt_net/core/remote_store/google/__init__.py +11 -0
  30. pygpt_net/core/remote_store/google/files.py +224 -0
  31. pygpt_net/core/remote_store/google/store.py +248 -0
  32. pygpt_net/core/remote_store/openai/__init__.py +11 -0
  33. pygpt_net/core/{assistants → remote_store/openai}/files.py +26 -19
  34. pygpt_net/core/{assistants → remote_store/openai}/store.py +32 -15
  35. pygpt_net/core/remote_store/remote_store.py +24 -0
  36. pygpt_net/data/config/config.json +8 -4
  37. pygpt_net/data/config/models.json +77 -3
  38. pygpt_net/data/config/settings.json +45 -0
  39. pygpt_net/data/locale/locale.de.ini +41 -41
  40. pygpt_net/data/locale/locale.en.ini +53 -43
  41. pygpt_net/data/locale/locale.es.ini +41 -41
  42. pygpt_net/data/locale/locale.fr.ini +41 -41
  43. pygpt_net/data/locale/locale.it.ini +41 -41
  44. pygpt_net/data/locale/locale.pl.ini +42 -42
  45. pygpt_net/data/locale/locale.uk.ini +41 -41
  46. pygpt_net/data/locale/locale.zh.ini +41 -41
  47. pygpt_net/data/locale/plugin.cmd_history.de.ini +1 -1
  48. pygpt_net/data/locale/plugin.cmd_history.en.ini +1 -1
  49. pygpt_net/data/locale/plugin.cmd_history.es.ini +1 -1
  50. pygpt_net/data/locale/plugin.cmd_history.fr.ini +1 -1
  51. pygpt_net/data/locale/plugin.cmd_history.it.ini +1 -1
  52. pygpt_net/data/locale/plugin.cmd_history.pl.ini +1 -1
  53. pygpt_net/data/locale/plugin.cmd_history.uk.ini +1 -1
  54. pygpt_net/data/locale/plugin.cmd_history.zh.ini +1 -1
  55. pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +14 -0
  56. pygpt_net/data/locale/plugin.cmd_web.de.ini +1 -1
  57. pygpt_net/data/locale/plugin.cmd_web.en.ini +1 -1
  58. pygpt_net/data/locale/plugin.cmd_web.es.ini +1 -1
  59. pygpt_net/data/locale/plugin.cmd_web.fr.ini +1 -1
  60. pygpt_net/data/locale/plugin.cmd_web.it.ini +1 -1
  61. pygpt_net/data/locale/plugin.cmd_web.pl.ini +1 -1
  62. pygpt_net/data/locale/plugin.cmd_web.uk.ini +1 -1
  63. pygpt_net/data/locale/plugin.cmd_web.zh.ini +1 -1
  64. pygpt_net/data/locale/plugin.idx_llama_index.de.ini +2 -2
  65. pygpt_net/data/locale/plugin.idx_llama_index.en.ini +2 -2
  66. pygpt_net/data/locale/plugin.idx_llama_index.es.ini +2 -2
  67. pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +2 -2
  68. pygpt_net/data/locale/plugin.idx_llama_index.it.ini +2 -2
  69. pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +2 -2
  70. pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +2 -2
  71. pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +2 -2
  72. pygpt_net/item/assistant.py +1 -211
  73. pygpt_net/item/ctx.py +3 -1
  74. pygpt_net/item/store.py +238 -0
  75. pygpt_net/migrations/Version20260102190000.py +35 -0
  76. pygpt_net/migrations/__init__.py +3 -1
  77. pygpt_net/plugin/cmd_mouse_control/config.py +470 -1
  78. pygpt_net/plugin/cmd_mouse_control/plugin.py +488 -22
  79. pygpt_net/plugin/cmd_mouse_control/worker.py +464 -87
  80. pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +729 -0
  81. pygpt_net/plugin/idx_llama_index/config.py +2 -2
  82. pygpt_net/provider/api/google/__init__.py +16 -54
  83. pygpt_net/provider/api/google/chat.py +546 -129
  84. pygpt_net/provider/api/google/computer.py +190 -0
  85. pygpt_net/provider/api/google/realtime/realtime.py +2 -2
  86. pygpt_net/provider/api/google/remote_tools.py +93 -0
  87. pygpt_net/provider/api/google/store.py +546 -0
  88. pygpt_net/provider/api/google/worker/__init__.py +0 -0
  89. pygpt_net/provider/api/google/worker/importer.py +392 -0
  90. pygpt_net/provider/api/openai/computer.py +10 -1
  91. pygpt_net/provider/api/openai/store.py +6 -6
  92. pygpt_net/provider/api/openai/worker/importer.py +24 -24
  93. pygpt_net/provider/core/config/patch.py +16 -1
  94. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +3 -3
  95. pygpt_net/provider/core/model/patch.py +17 -3
  96. pygpt_net/provider/core/preset/json_file.py +13 -7
  97. pygpt_net/provider/core/{assistant_file → remote_file}/__init__.py +1 -1
  98. pygpt_net/provider/core/{assistant_file → remote_file}/base.py +9 -9
  99. pygpt_net/provider/core/remote_file/db_sqlite/__init__.py +12 -0
  100. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/patch.py +1 -1
  101. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/provider.py +23 -20
  102. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/storage.py +35 -27
  103. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/utils.py +5 -4
  104. pygpt_net/provider/core/{assistant_store → remote_store}/__init__.py +1 -1
  105. pygpt_net/provider/core/{assistant_store → remote_store}/base.py +10 -10
  106. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/__init__.py +1 -1
  107. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/patch.py +1 -1
  108. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/provider.py +16 -15
  109. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/storage.py +30 -23
  110. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/utils.py +5 -4
  111. pygpt_net/provider/core/{assistant_store → remote_store}/json_file.py +9 -9
  112. pygpt_net/provider/llms/google.py +2 -2
  113. pygpt_net/ui/base/config_dialog.py +3 -2
  114. pygpt_net/ui/dialog/assistant.py +3 -3
  115. pygpt_net/ui/dialog/plugins.py +3 -1
  116. pygpt_net/ui/dialog/remote_store_google.py +539 -0
  117. pygpt_net/ui/dialog/{assistant_store.py → remote_store_openai.py} +95 -95
  118. pygpt_net/ui/dialogs.py +5 -3
  119. pygpt_net/ui/layout/chat/attachments_uploaded.py +3 -3
  120. pygpt_net/ui/layout/toolbox/computer_env.py +26 -8
  121. pygpt_net/ui/menu/tools.py +13 -5
  122. pygpt_net/ui/widget/dialog/remote_store_google.py +56 -0
  123. pygpt_net/ui/widget/dialog/{assistant_store.py → remote_store_openai.py} +9 -9
  124. pygpt_net/ui/widget/element/button.py +4 -4
  125. pygpt_net/ui/widget/lists/remote_store_google.py +248 -0
  126. pygpt_net/ui/widget/lists/{assistant_store.py → remote_store_openai.py} +21 -21
  127. pygpt_net/ui/widget/option/checkbox_list.py +47 -9
  128. pygpt_net/ui/widget/option/combo.py +39 -3
  129. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/METADATA +33 -2
  130. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/RECORD +133 -108
  131. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/LICENSE +0 -0
  132. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/WHEEL +0 -0
  133. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2026.01.02 20:00:00 #
10
+ # ================================================== #
11
+
12
+ from typing import Optional, List, Dict, Union
13
+
14
+ from packaging.version import Version
15
+
16
+ from pygpt_net.item.assistant import AssistantItem
17
+ from pygpt_net.item.store import RemoteFileItem
18
+ from pygpt_net.provider.core.remote_file.db_sqlite import DbSqliteProvider
19
+
20
+
21
+ class Files:
22
+
23
+ PROVIDER_NAME = "google"
24
+
25
+ def __init__(self, window=None):
26
+ """
27
+ Google remote files core (File Search documents)
28
+
29
+ :param window: Window instance
30
+ """
31
+ self.window = window
32
+ self.provider = DbSqliteProvider(window)
33
+ self.items = {}
34
+
35
+ def install(self):
36
+ self.provider.install()
37
+
38
+ def patch(self, app_version: Version) -> bool:
39
+ return self.provider.patch(app_version)
40
+
41
+ def get(self, id: str) -> Optional[RemoteFileItem]:
42
+ if id in self.items:
43
+ return self.items[id]
44
+ return None
45
+
46
+ def get_ids(self) -> List[str]:
47
+ return list(self.items.keys())
48
+
49
+ def get_all(self) -> Dict[str, RemoteFileItem]:
50
+ return self.items
51
+
52
+ def get_id_by_idx_all(self, idx: int) -> str:
53
+ return list(self.items.keys())[idx]
54
+
55
+ def get_by_idx(self, idx: int) -> str:
56
+ items = self.items
57
+ return list(items.keys())[idx]
58
+
59
+ def has(self, id: str) -> bool:
60
+ return id in self.items
61
+
62
+ def create(
63
+ self,
64
+ assistant: AssistantItem,
65
+ thread_id: str,
66
+ file_id: str,
67
+ name: str,
68
+ path: str,
69
+ size: int) -> Optional[RemoteFileItem]:
70
+ """
71
+ Not used in Google path (kept for parity). Use insert() instead.
72
+ """
73
+ file = RemoteFileItem()
74
+ file.id = file_id
75
+ file.file_id = file_id
76
+ file.thread_id = thread_id
77
+ file.provider = self.PROVIDER_NAME
78
+ file.name = name
79
+ file.path = path
80
+ file.size = size
81
+ if assistant.vector_store is not None and assistant.vector_store != "":
82
+ file.store_id = assistant.vector_store
83
+ file.record_id = self.provider.create(file)
84
+ self.items[file.id] = file
85
+ return file
86
+
87
+ def update(self, file: RemoteFileItem) -> Optional[RemoteFileItem]:
88
+ self.items[file.id] = file
89
+ self.provider.save(file)
90
+ return file
91
+
92
+ def get_names(self) -> Dict[str, str]:
93
+ names = {}
94
+ for id in self.items:
95
+ file = self.items[id]
96
+ names[id] = file.name
97
+ return names
98
+
99
+ def get_by_store_or_thread(self, store_id: str, thread_id: str) -> Dict[str, RemoteFileItem]:
100
+ return self.provider.get_by_store_or_thread(store_id, thread_id)
101
+
102
+ def count_by_store_or_thread(self, store_id: str, thread_id: str) -> int:
103
+ return self.provider.count_by_store_or_thread(store_id, thread_id)
104
+
105
+ def get_file_by_idx(self, idx: int, store_id: str, thread_id: str) -> Optional[RemoteFileItem]:
106
+ files = self.get_by_store_or_thread(store_id, thread_id)
107
+ if idx >= len(files):
108
+ return None
109
+ return list(files.values())[idx]
110
+
111
+ def get_file_id_by_idx(self, idx: int, store_id: str, thread_id: str) -> Optional[str]:
112
+ file = self.get_file_by_idx(idx, store_id, thread_id)
113
+ if file is None:
114
+ return None
115
+ return file.file_id
116
+
117
+ def get_all_by_file_id(self, file_id: str) -> dict:
118
+ return self.provider.get_all_by_file_id(file_id)
119
+
120
+ def delete(self, file: Union[RemoteFileItem, list]) -> bool:
121
+ files = file if isinstance(file, list) else [file]
122
+ for f in files:
123
+ file_id = f.file_id # document name (fileSearchStores/.../documents/..)
124
+ items = self.get_all_by_file_id(file_id)
125
+ for id in items:
126
+ store_id = items[id].store_id
127
+ if store_id is None or store_id == "":
128
+ continue
129
+ try:
130
+ self.window.core.api.google.store.delete_store_file(store_id, file_id)
131
+ except Exception as e:
132
+ self.window.core.debug.log("Failed to delete document from store: " + str(e))
133
+ self.provider.delete_by_id(f.record_id)
134
+ try:
135
+ # There is no Files API deletion here; this is store document
136
+ pass
137
+ except Exception as e:
138
+ self.window.core.debug.log("Failed to delete remote document: " + str(e))
139
+ if f.record_id in self.items:
140
+ del self.items[f.record_id]
141
+ return True
142
+
143
+ def delete_by_file_id(self, file_id: str) -> bool:
144
+ res = self.provider.delete_by_file_id(file_id)
145
+ if res:
146
+ to_delete = []
147
+ for id in self.items:
148
+ if self.items[id].file_id == file_id:
149
+ to_delete.append(id)
150
+ for id in to_delete:
151
+ del self.items[id]
152
+ return res
153
+
154
+ def on_store_deleted(self, store_id: str):
155
+ self.provider.clear_store_from_files(store_id)
156
+
157
+ def on_all_stores_deleted(self):
158
+ self.provider.clear_all_stores_from_files(self.PROVIDER_NAME)
159
+
160
+ def rename(self, record_id: int, name: str) -> bool:
161
+ self.provider.rename_file(record_id, name)
162
+ if record_id in self.items:
163
+ self.items[record_id].name = name
164
+ return True
165
+
166
+ def truncate(self, store_id: Optional[str] = None) -> bool:
167
+ if store_id is not None:
168
+ self.window.core.api.google.store.remove_from_store(store_id)
169
+ else:
170
+ self.window.core.api.google.store.remove_from_stores()
171
+ return self.truncate_local(store_id)
172
+
173
+ def truncate_local(self, store_id: Optional[str] = None) -> bool:
174
+ if store_id is not None:
175
+ self.provider.truncate_by_store(store_id)
176
+ else:
177
+ self.provider.truncate_all(self.PROVIDER_NAME)
178
+ self.items = {}
179
+ return True
180
+
181
+ def import_from_store(self, store_id: str) -> bool:
182
+ files = self.window.core.api.google.store.import_store_files(store_id)
183
+ for file in files:
184
+ # Not used in this path
185
+ pass
186
+ return True
187
+
188
+ def insert(self, store_id: str, data) -> RemoteFileItem:
189
+ """
190
+ Insert a Document into local DB
191
+
192
+ :param store_id: store name ('fileSearchStores/...').
193
+ :param data: document object from API
194
+ """
195
+ file = RemoteFileItem()
196
+ # Use document name as unique id and file_id
197
+ file.id = getattr(data, "name", None)
198
+ file.file_id = getattr(data, "name", None)
199
+ file.thread_id = ""
200
+ file.name = getattr(data, "display_name", None) or getattr(data, "name", "")
201
+ file.provider = self.PROVIDER_NAME
202
+ file.path = getattr(data, "display_name", None) or getattr(data, "name", "")
203
+ # sizeBytes is a string (int64), convert if possible
204
+ try:
205
+ file.size = int(getattr(data, "size_bytes", 0) or 0)
206
+ except Exception:
207
+ file.size = 0
208
+ file.store_id = store_id
209
+ file.record_id = self.provider.create(file)
210
+ self.items[file.id] = file
211
+ return file
212
+
213
+ def load(self):
214
+ self.items = self.provider.load_all(self.PROVIDER_NAME)
215
+ self.sort_items()
216
+
217
+ def sort_items(self):
218
+ return
219
+
220
+ def save(self):
221
+ self.provider.save_all(self.items)
222
+
223
+ def get_version(self) -> str:
224
+ return self.provider.get_version()
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2026.01.02 20:00:00 #
10
+ # ================================================== #
11
+
12
+ import datetime
13
+ from typing import Optional, List, Dict, Any
14
+
15
+ from packaging.version import Version
16
+
17
+ from pygpt_net.item.store import RemoteStoreItem
18
+ from pygpt_net.provider.core.remote_store.db_sqlite import DbSqliteProvider
19
+
20
+ from .files import Files
21
+
22
+
23
+ class Store:
24
+
25
+ PROVIDER_NAME = "google"
26
+
27
+ def __init__(self, window=None):
28
+ """
29
+ Google File Search store core
30
+
31
+ :param window: Window instance
32
+ """
33
+ self.window = window
34
+ self.provider = DbSqliteProvider(window)
35
+ self.files = Files(window)
36
+ self.items = {}
37
+
38
+ def install(self):
39
+ """Install provider data"""
40
+ self.provider.install()
41
+ self.files.install()
42
+
43
+ def patch(self, app_version: Version) -> bool:
44
+ """Patch provider data"""
45
+ res1 = self.files.patch(app_version)
46
+ res2 = self.provider.patch(app_version)
47
+ return res1 or res2
48
+
49
+ def get(self, id: str) -> RemoteStoreItem:
50
+ if id in self.items:
51
+ return self.items[id]
52
+
53
+ def get_ids(self) -> List[str]:
54
+ return list(self.items.keys())
55
+
56
+ def get_all(self) -> Dict[str, RemoteStoreItem]:
57
+ return self.items
58
+
59
+ def get_id_by_idx_all(self, idx: int) -> str:
60
+ return list(self.items.keys())[idx]
61
+
62
+ def get_by_idx(self, idx: int) -> str:
63
+ items = self.items
64
+ return list(items.keys())[idx]
65
+
66
+ def has(self, id: str) -> bool:
67
+ return id in self.items
68
+
69
+ def create(self, name: Optional[str] = None) -> Optional[RemoteStoreItem]:
70
+ """
71
+ Create new File Search store
72
+
73
+ :param name: display name for the store (also used as local alias)
74
+ :return: store item
75
+ """
76
+ # Use provided name or fallback
77
+ display_name = name or "New file search store"
78
+
79
+ # Create remote store with a display name
80
+ vector_store = self.window.core.api.google.store.create_store(display_name, 0)
81
+ if vector_store is None:
82
+ return None
83
+
84
+ # Build local item; set local alias to the provided name
85
+ store = RemoteStoreItem()
86
+ store.id = getattr(vector_store, "name", None) # 'fileSearchStores/...'
87
+ # Keep local alias as the passed name; fallback to remote display_name if missing
88
+ store.name = display_name or (getattr(vector_store, "display_name", None) or "")
89
+ store.provider = self.PROVIDER_NAME
90
+ store.is_thread = False
91
+ store.record_id = self.provider.create(store)
92
+ self.items[store.id] = store
93
+ return store
94
+
95
+ def update(self, store: RemoteStoreItem) -> Optional[RemoteStoreItem]:
96
+ """
97
+ Update store (local persist; remote update is not supported by Google)
98
+ """
99
+ vector_store = self.window.core.api.google.store.update_store(store.id, store.name, store.expire_days)
100
+ # Always persist local metadata (alias)
101
+ self.items[store.id] = store
102
+ self.provider.save(store)
103
+ return store
104
+
105
+ def get_status_data(self, id: str):
106
+ """
107
+ Get store status data
108
+
109
+ :param id: store id
110
+ :return: status data, store data
111
+ """
112
+ status = {}
113
+ data = self.window.core.api.google.store.get_store(id)
114
+ if data is not None:
115
+ status = self.parse_status(data)
116
+ return status, data
117
+
118
+ def parse_status(self, store) -> Dict[str, Any]:
119
+ """
120
+ Map Google File Search store status to local fields
121
+ """
122
+ def _to_int(x):
123
+ try:
124
+ return int(x or 0)
125
+ except Exception:
126
+ return 0
127
+
128
+ active = _to_int(getattr(store, "active_documents_count", 0))
129
+ pending = _to_int(getattr(store, "pending_documents_count", 0))
130
+ failed = _to_int(getattr(store, "failed_documents_count", 0))
131
+ size_bytes = _to_int(getattr(store, "size_bytes", 0))
132
+ update_time = getattr(store, "update_time", None)
133
+ display_name = getattr(store, "display_name", None) or ""
134
+
135
+ status_str = "ready"
136
+ if pending > 0:
137
+ status_str = "indexing"
138
+ if failed > 0 and active == 0 and pending == 0:
139
+ status_str = "failed"
140
+
141
+ status = {
142
+ "status": status_str,
143
+ "usage_bytes": size_bytes,
144
+ "expires_at": None,
145
+ "last_active_at": self._parse_rfc3339_to_epoch(update_time) if update_time else None,
146
+ "file_counts": {
147
+ "in_progress": pending,
148
+ "completed": active,
149
+ "cancelled": 0,
150
+ "failed": failed,
151
+ "total": active + pending + failed,
152
+ },
153
+ "expires_after": None,
154
+ "remote_display_name": display_name,
155
+ }
156
+ return status
157
+
158
+ def _parse_rfc3339_to_epoch(self, s: str) -> int:
159
+ """Convert RFC3339 to epoch seconds if possible"""
160
+ try:
161
+ s2 = s.replace('Z', '+00:00')
162
+ ts = datetime.datetime.fromisoformat(s2)
163
+ return int(ts.timestamp())
164
+ except Exception:
165
+ return int(datetime.datetime.now().timestamp())
166
+
167
+ def update_status(self, id: str):
168
+ """
169
+ Update store status and keep local alias if set
170
+ """
171
+ store = self.items[id]
172
+ status, data = self.get_status_data(id)
173
+ remote_name = getattr(data, "display_name", None) if data else None
174
+ # Keep local alias if user set it; otherwise adopt remote display name
175
+ if not store.name or store.name.strip() == "":
176
+ store.name = remote_name or ""
177
+ store.provider = self.PROVIDER_NAME
178
+ self.append_status(store, status)
179
+ self.update(store)
180
+
181
+ def append_status(self, store: RemoteStoreItem, status: Dict[str, Any]):
182
+ now = datetime.datetime.now()
183
+ ts = int(now.timestamp())
184
+ status["__last_refresh__"] = now.strftime("%Y-%m-%d %H:%M:%S")
185
+ store.status = status
186
+ store.last_sync = ts
187
+ if "status" in status:
188
+ store.last_status = status["status"]
189
+ if "usage_bytes" in status:
190
+ store.usage_bytes = status["usage_bytes"]
191
+ if "file_counts" in status:
192
+ store.num_files = status["file_counts"]["total"]
193
+ if "last_active_at" in status and status["last_active_at"]:
194
+ store.last_active = int(status["last_active_at"])
195
+
196
+ def get_names(self) -> Dict[str, str]:
197
+ names = {}
198
+ for id in self.items:
199
+ store = self.items[id]
200
+ names[id] = store.name
201
+ return names
202
+
203
+ def delete(self, id: str) -> bool:
204
+ if id in self.items:
205
+ store = self.items[id]
206
+ self.provider.delete_by_id(store.record_id)
207
+ self.window.core.api.google.store.remove_store(id)
208
+ del self.items[id]
209
+ return True
210
+ return False
211
+
212
+ def import_items(self, items: Dict[str, RemoteStoreItem]):
213
+ self.items = items
214
+ for item in items.values():
215
+ item.provider = self.PROVIDER_NAME
216
+ item.record_id = self.provider.create(item)
217
+
218
+ def clear(self):
219
+ self.truncate()
220
+
221
+ def is_hidden(self, id: str) -> bool:
222
+ if id in self.items:
223
+ if (self.window.core.config.get("remote_store.google.hide_threads")
224
+ and (self.items[id].name is None or self.items[id].name == "")):
225
+ return True
226
+ return False
227
+
228
+ def truncate(self) -> bool:
229
+ self.provider.truncate(self.PROVIDER_NAME)
230
+ self.items = {}
231
+ return True
232
+
233
+ def load(self):
234
+ self.items = self.provider.load_all(self.PROVIDER_NAME)
235
+ self.sort_items()
236
+
237
+ def load_all(self):
238
+ self.load()
239
+ self.files.load()
240
+
241
+ def sort_items(self):
242
+ pass
243
+
244
+ def save(self):
245
+ self.provider.save_all(self.items)
246
+
247
+ def get_version(self) -> str:
248
+ return self.provider.get_version()
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2026.01.02 20:00:00 #
10
+ # ================================================== #
11
+ from .store import *
@@ -6,21 +6,25 @@
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.12.27 00:00:00 #
9
+ # Updated Date: 2026.01.02 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List, Dict, Union
13
13
 
14
14
  from packaging.version import Version
15
15
 
16
- from pygpt_net.item.assistant import AssistantFileItem, AssistantItem
17
- from pygpt_net.provider.core.assistant_file.db_sqlite import DbSqliteProvider
16
+ from pygpt_net.item.assistant import AssistantItem
17
+ from pygpt_net.item.store import RemoteFileItem
18
+ from pygpt_net.provider.core.remote_file.db_sqlite import DbSqliteProvider
18
19
 
19
20
 
20
21
  class Files:
22
+
23
+ PROVIDER_NAME = "openai"
24
+
21
25
  def __init__(self, window=None):
22
26
  """
23
- Assistant files core
27
+ OpenAI remote files core
24
28
 
25
29
  :param window: Window instance
26
30
  """
@@ -41,7 +45,7 @@ class Files:
41
45
  """
42
46
  return self.provider.patch(app_version)
43
47
 
44
- def get(self, id: str) -> AssistantFileItem:
48
+ def get(self, id: str) -> Optional[RemoteFileItem]:
45
49
  """
46
50
  Get file item by file_id
47
51
 
@@ -50,6 +54,7 @@ class Files:
50
54
  """
51
55
  if id in self.items:
52
56
  return self.items[id]
57
+ return None
53
58
 
54
59
  def get_ids(self) -> List[str]:
55
60
  """
@@ -60,7 +65,7 @@ class Files:
60
65
  return list(self.items.keys())
61
66
 
62
67
 
63
- def get_all(self) -> Dict[str, AssistantFileItem]:
68
+ def get_all(self) -> Dict[str, RemoteFileItem]:
64
69
  """
65
70
  Return all files
66
71
 
@@ -105,7 +110,7 @@ class Files:
105
110
  file_id: str,
106
111
  name: str,
107
112
  path: str,
108
- size: int) -> Optional[AssistantFileItem]:
113
+ size: int) -> Optional[RemoteFileItem]:
109
114
  """
110
115
  Create new file
111
116
 
@@ -117,10 +122,11 @@ class Files:
117
122
  :param size: file size
118
123
  :return: file item
119
124
  """
120
- file = AssistantFileItem()
125
+ file = RemoteFileItem()
121
126
  file.id = file_id
122
127
  file.file_id = file_id
123
128
  file.thread_id = thread_id
129
+ file.provider = self.PROVIDER_NAME
124
130
  file.name = name
125
131
  file.path = path
126
132
  file.size = size
@@ -132,8 +138,8 @@ class Files:
132
138
 
133
139
  def update(
134
140
  self,
135
- file: AssistantFileItem
136
- ) -> Optional[AssistantFileItem]:
141
+ file: RemoteFileItem
142
+ ) -> Optional[RemoteFileItem]:
137
143
  """
138
144
  Update file
139
145
 
@@ -160,7 +166,7 @@ class Files:
160
166
  self,
161
167
  store_id: str,
162
168
  thread_id: str
163
- ) -> Dict[str, AssistantFileItem]:
169
+ ) -> Dict[str, RemoteFileItem]:
164
170
  """
165
171
  Get files by store or thread
166
172
 
@@ -189,7 +195,7 @@ class Files:
189
195
  idx: int,
190
196
  store_id: str,
191
197
  thread_id: str
192
- ) -> Optional[AssistantFileItem]:
198
+ ) -> Optional[RemoteFileItem]:
193
199
  """
194
200
  Get file by list index
195
201
 
@@ -231,7 +237,7 @@ class Files:
231
237
  """
232
238
  return self.provider.get_all_by_file_id(file_id)
233
239
 
234
- def delete(self, file: Union[AssistantFileItem, list]) -> bool:
240
+ def delete(self, file: Union[RemoteFileItem, list]) -> bool:
235
241
  """
236
242
  Delete file and remove from vector stores if exists
237
243
 
@@ -287,7 +293,7 @@ class Files:
287
293
 
288
294
  def on_all_stores_deleted(self):
289
295
  """Clear all deleted stores from files"""
290
- self.provider.clear_all_stores_from_files()
296
+ self.provider.clear_all_stores_from_files(self.PROVIDER_NAME)
291
297
 
292
298
  def rename(self, record_id: int, name: str) -> bool:
293
299
  """
@@ -325,7 +331,7 @@ class Files:
325
331
  if store_id is not None:
326
332
  self.provider.truncate_by_store(store_id) # truncate files in DB (by store_id)
327
333
  else:
328
- self.provider.truncate_all() # truncate all files in DB
334
+ self.provider.truncate_all(self.PROVIDER_NAME) # truncate all files in DB
329
335
  self.items = {} # clear items
330
336
  return True
331
337
 
@@ -341,18 +347,19 @@ class Files:
341
347
  self.create(file.assistant, file.thread_id, file.file_id, file.name, file.path, file.size)
342
348
  return True
343
349
 
344
- def insert(self, store_id: str, data) -> AssistantFileItem:
350
+ def insert(self, store_id: str, data) -> RemoteFileItem:
345
351
  """
346
352
  Insert file object
347
353
 
348
354
  :param store_id: store ID
349
355
  :param data: file data from API
350
356
  """
351
- file = AssistantFileItem()
357
+ file = RemoteFileItem()
352
358
  file.id = data.id
353
359
  file.file_id = data.id
354
360
  file.thread_id = ""
355
361
  file.name = data.filename
362
+ file.provider = self.PROVIDER_NAME
356
363
  file.path = data.filename
357
364
  file.size = data.bytes
358
365
  file.store_id = store_id
@@ -361,8 +368,8 @@ class Files:
361
368
  return file
362
369
 
363
370
  def load(self):
364
- """Load file"""
365
- self.items = self.provider.load_all()
371
+ """Load files"""
372
+ self.items = self.provider.load_all(self.PROVIDER_NAME)
366
373
  self.sort_items()
367
374
 
368
375
  def sort_items(self):