pygpt-net 2.7.4__py3-none-any.whl → 2.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +15 -0
- pygpt_net/__init__.py +4 -4
- pygpt_net/app_core.py +4 -2
- pygpt_net/controller/__init__.py +5 -1
- pygpt_net/controller/assistant/assistant.py +1 -4
- pygpt_net/controller/assistant/batch.py +5 -504
- pygpt_net/controller/assistant/editor.py +5 -5
- pygpt_net/controller/assistant/files.py +16 -16
- pygpt_net/controller/chat/handler/google_stream.py +307 -1
- pygpt_net/controller/chat/handler/worker.py +10 -25
- pygpt_net/controller/chat/handler/xai_stream.py +621 -52
- pygpt_net/controller/chat/image.py +2 -2
- pygpt_net/controller/debug/fixtures.py +3 -2
- pygpt_net/controller/dialogs/confirm.py +73 -101
- pygpt_net/controller/files/files.py +65 -4
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/painter/capture.py +50 -1
- pygpt_net/controller/presets/presets.py +2 -1
- pygpt_net/controller/remote_store/__init__.py +12 -0
- pygpt_net/{provider/core/assistant_file/db_sqlite → controller/remote_store/google}/__init__.py +2 -2
- pygpt_net/controller/remote_store/google/batch.py +402 -0
- pygpt_net/controller/remote_store/google/store.py +615 -0
- pygpt_net/controller/remote_store/openai/__init__.py +12 -0
- pygpt_net/controller/remote_store/openai/batch.py +524 -0
- pygpt_net/controller/{assistant → remote_store/openai}/store.py +63 -60
- pygpt_net/controller/remote_store/remote_store.py +35 -0
- pygpt_net/controller/ui/ui.py +20 -1
- pygpt_net/core/assistants/assistants.py +3 -15
- pygpt_net/core/db/database.py +5 -3
- pygpt_net/core/filesystem/url.py +4 -1
- pygpt_net/core/locale/placeholder.py +35 -0
- pygpt_net/core/remote_store/__init__.py +12 -0
- pygpt_net/core/remote_store/google/__init__.py +11 -0
- pygpt_net/core/remote_store/google/files.py +224 -0
- pygpt_net/core/remote_store/google/store.py +248 -0
- pygpt_net/core/remote_store/openai/__init__.py +11 -0
- pygpt_net/core/{assistants → remote_store/openai}/files.py +26 -19
- pygpt_net/core/{assistants → remote_store/openai}/store.py +32 -15
- pygpt_net/core/remote_store/remote_store.py +24 -0
- pygpt_net/core/render/web/body.py +3 -2
- pygpt_net/core/types/chunk.py +27 -0
- pygpt_net/data/config/config.json +8 -4
- pygpt_net/data/config/models.json +77 -3
- pygpt_net/data/config/settings.json +45 -0
- pygpt_net/data/js/app/template.js +1 -1
- pygpt_net/data/js/app.min.js +2 -2
- pygpt_net/data/locale/locale.de.ini +44 -41
- pygpt_net/data/locale/locale.en.ini +56 -43
- pygpt_net/data/locale/locale.es.ini +44 -41
- pygpt_net/data/locale/locale.fr.ini +44 -41
- pygpt_net/data/locale/locale.it.ini +44 -41
- pygpt_net/data/locale/locale.pl.ini +45 -42
- pygpt_net/data/locale/locale.uk.ini +44 -41
- pygpt_net/data/locale/locale.zh.ini +44 -41
- pygpt_net/data/locale/plugin.cmd_history.de.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.en.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.es.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.fr.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.it.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.pl.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.uk.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.zh.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +14 -0
- pygpt_net/data/locale/plugin.cmd_web.de.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.en.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.es.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.fr.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.it.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.pl.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.uk.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.zh.ini +1 -1
- pygpt_net/data/locale/plugin.idx_llama_index.de.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.en.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.es.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.it.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +2 -2
- pygpt_net/item/assistant.py +1 -211
- pygpt_net/item/ctx.py +3 -3
- pygpt_net/item/store.py +238 -0
- pygpt_net/js_rc.py +2449 -2447
- pygpt_net/migrations/Version20260102190000.py +35 -0
- pygpt_net/migrations/__init__.py +3 -1
- pygpt_net/plugin/cmd_mouse_control/config.py +471 -1
- pygpt_net/plugin/cmd_mouse_control/plugin.py +487 -22
- pygpt_net/plugin/cmd_mouse_control/worker.py +464 -87
- pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +729 -0
- pygpt_net/plugin/idx_llama_index/config.py +2 -2
- pygpt_net/provider/api/anthropic/__init__.py +10 -8
- pygpt_net/provider/api/google/__init__.py +21 -58
- pygpt_net/provider/api/google/chat.py +545 -129
- pygpt_net/provider/api/google/computer.py +190 -0
- pygpt_net/provider/api/google/realtime/realtime.py +2 -2
- pygpt_net/provider/api/google/remote_tools.py +93 -0
- pygpt_net/provider/api/google/store.py +546 -0
- pygpt_net/provider/api/google/worker/__init__.py +0 -0
- pygpt_net/provider/api/google/worker/importer.py +392 -0
- pygpt_net/provider/api/openai/__init__.py +7 -3
- pygpt_net/provider/api/openai/computer.py +10 -1
- pygpt_net/provider/api/openai/responses.py +0 -0
- pygpt_net/provider/api/openai/store.py +6 -6
- pygpt_net/provider/api/openai/worker/importer.py +24 -24
- pygpt_net/provider/api/x_ai/__init__.py +10 -9
- pygpt_net/provider/api/x_ai/chat.py +272 -102
- pygpt_net/provider/core/config/patch.py +16 -1
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +3 -3
- pygpt_net/provider/core/model/patch.py +17 -3
- pygpt_net/provider/core/preset/json_file.py +13 -7
- pygpt_net/provider/core/{assistant_file → remote_file}/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_file → remote_file}/base.py +9 -9
- pygpt_net/provider/core/remote_file/db_sqlite/__init__.py +12 -0
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/patch.py +1 -1
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/provider.py +23 -20
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/storage.py +35 -27
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/utils.py +5 -4
- pygpt_net/provider/core/{assistant_store → remote_store}/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/base.py +10 -10
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/patch.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/provider.py +16 -15
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/storage.py +30 -23
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/utils.py +5 -4
- pygpt_net/provider/core/{assistant_store → remote_store}/json_file.py +9 -9
- pygpt_net/provider/llms/google.py +2 -2
- pygpt_net/tools/image_viewer/ui/dialogs.py +298 -12
- pygpt_net/tools/text_editor/ui/widgets.py +5 -1
- pygpt_net/ui/base/config_dialog.py +3 -2
- pygpt_net/ui/base/context_menu.py +44 -1
- pygpt_net/ui/dialog/assistant.py +3 -3
- pygpt_net/ui/dialog/plugins.py +3 -1
- pygpt_net/ui/dialog/remote_store_google.py +539 -0
- pygpt_net/ui/dialog/{assistant_store.py → remote_store_openai.py} +95 -95
- pygpt_net/ui/dialogs.py +5 -3
- pygpt_net/ui/layout/chat/attachments_uploaded.py +3 -3
- pygpt_net/ui/layout/toolbox/computer_env.py +26 -8
- pygpt_net/ui/layout/toolbox/indexes.py +22 -19
- pygpt_net/ui/layout/toolbox/model.py +28 -5
- pygpt_net/ui/menu/tools.py +13 -5
- pygpt_net/ui/widget/dialog/remote_store_google.py +56 -0
- pygpt_net/ui/widget/dialog/{assistant_store.py → remote_store_openai.py} +9 -9
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +25 -8
- pygpt_net/ui/widget/lists/remote_store_google.py +248 -0
- pygpt_net/ui/widget/lists/{assistant_store.py → remote_store_openai.py} +21 -21
- pygpt_net/ui/widget/option/checkbox_list.py +47 -9
- pygpt_net/ui/widget/option/combo.py +39 -3
- pygpt_net/ui/widget/tabs/output.py +9 -1
- pygpt_net/ui/widget/textarea/editor.py +14 -1
- pygpt_net/ui/widget/textarea/input.py +20 -7
- pygpt_net/ui/widget/textarea/notepad.py +24 -1
- pygpt_net/ui/widget/textarea/output.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +16 -1
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/METADATA +41 -2
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/RECORD +158 -132
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.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:
|
|
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
|
|
17
|
-
from pygpt_net.
|
|
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
|
-
|
|
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) ->
|
|
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,
|
|
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[
|
|
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 =
|
|
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:
|
|
136
|
-
) -> Optional[
|
|
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,
|
|
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[
|
|
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[
|
|
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) ->
|
|
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 =
|
|
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
|
|
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):
|