pygpt-net 2.7.6__py3-none-any.whl → 2.7.8__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 (120) hide show
  1. pygpt_net/CHANGELOG.txt +13 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +5 -1
  4. pygpt_net/controller/assistant/batch.py +2 -2
  5. pygpt_net/controller/assistant/files.py +7 -6
  6. pygpt_net/controller/assistant/threads.py +0 -0
  7. pygpt_net/controller/chat/command.py +0 -0
  8. pygpt_net/controller/chat/remote_tools.py +3 -9
  9. pygpt_net/controller/chat/stream.py +2 -2
  10. pygpt_net/controller/chat/{handler/worker.py → stream_worker.py} +13 -35
  11. pygpt_net/controller/dialogs/confirm.py +35 -58
  12. pygpt_net/controller/lang/mapping.py +9 -9
  13. pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
  14. pygpt_net/controller/remote_store/remote_store.py +982 -13
  15. pygpt_net/core/command/command.py +0 -0
  16. pygpt_net/core/db/viewer.py +1 -1
  17. pygpt_net/core/debug/models.py +2 -2
  18. pygpt_net/core/realtime/worker.py +3 -1
  19. pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
  20. pygpt_net/core/remote_store/anthropic/files.py +211 -0
  21. pygpt_net/core/remote_store/anthropic/store.py +208 -0
  22. pygpt_net/core/remote_store/openai/store.py +5 -4
  23. pygpt_net/core/remote_store/remote_store.py +5 -1
  24. pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
  25. pygpt_net/core/remote_store/xai/files.py +225 -0
  26. pygpt_net/core/remote_store/xai/store.py +219 -0
  27. pygpt_net/data/config/config.json +18 -5
  28. pygpt_net/data/config/models.json +193 -4
  29. pygpt_net/data/config/settings.json +179 -36
  30. pygpt_net/data/icons/folder_eye.svg +1 -0
  31. pygpt_net/data/icons/folder_eye_filled.svg +1 -0
  32. pygpt_net/data/icons/folder_open.svg +1 -0
  33. pygpt_net/data/icons/folder_open_filled.svg +1 -0
  34. pygpt_net/data/locale/locale.de.ini +6 -3
  35. pygpt_net/data/locale/locale.en.ini +46 -12
  36. pygpt_net/data/locale/locale.es.ini +6 -3
  37. pygpt_net/data/locale/locale.fr.ini +6 -3
  38. pygpt_net/data/locale/locale.it.ini +6 -3
  39. pygpt_net/data/locale/locale.pl.ini +7 -4
  40. pygpt_net/data/locale/locale.uk.ini +6 -3
  41. pygpt_net/data/locale/locale.zh.ini +6 -3
  42. pygpt_net/icons.qrc +4 -0
  43. pygpt_net/icons_rc.py +282 -138
  44. pygpt_net/plugin/cmd_mouse_control/worker.py +2 -1
  45. pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +2 -1
  46. pygpt_net/provider/api/anthropic/__init__.py +10 -3
  47. pygpt_net/provider/api/anthropic/chat.py +342 -11
  48. pygpt_net/provider/api/anthropic/computer.py +844 -0
  49. pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
  50. pygpt_net/provider/api/anthropic/store.py +307 -0
  51. pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +99 -10
  52. pygpt_net/provider/api/anthropic/tools.py +32 -77
  53. pygpt_net/provider/api/anthropic/utils.py +30 -0
  54. pygpt_net/{controller/chat/handler → provider/api/anthropic/worker}/__init__.py +0 -0
  55. pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
  56. pygpt_net/provider/api/google/chat.py +62 -9
  57. pygpt_net/provider/api/google/store.py +124 -3
  58. pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +92 -25
  59. pygpt_net/provider/api/google/utils.py +185 -0
  60. pygpt_net/provider/api/google/worker/importer.py +16 -28
  61. pygpt_net/provider/api/langchain/__init__.py +0 -0
  62. pygpt_net/{controller/chat/handler/langchain_stream.py → provider/api/langchain/stream.py} +1 -1
  63. pygpt_net/provider/api/llama_index/__init__.py +0 -0
  64. pygpt_net/{controller/chat/handler/llamaindex_stream.py → provider/api/llama_index/stream.py} +1 -1
  65. pygpt_net/provider/api/openai/assistants.py +2 -2
  66. pygpt_net/provider/api/openai/image.py +2 -2
  67. pygpt_net/provider/api/openai/store.py +4 -1
  68. pygpt_net/{controller/chat/handler/openai_stream.py → provider/api/openai/stream.py} +1 -1
  69. pygpt_net/provider/api/openai/utils.py +69 -3
  70. pygpt_net/provider/api/openai/worker/importer.py +19 -61
  71. pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
  72. pygpt_net/provider/api/x_ai/__init__.py +138 -15
  73. pygpt_net/provider/api/x_ai/audio.py +43 -11
  74. pygpt_net/provider/api/x_ai/chat.py +92 -4
  75. pygpt_net/provider/api/x_ai/image.py +149 -47
  76. pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
  77. pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
  78. pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
  79. pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +183 -70
  80. pygpt_net/provider/api/x_ai/responses.py +507 -0
  81. pygpt_net/provider/api/x_ai/store.py +610 -0
  82. pygpt_net/{controller/chat/handler/xai_stream.py → provider/api/x_ai/stream.py} +42 -10
  83. pygpt_net/provider/api/x_ai/tools.py +59 -8
  84. pygpt_net/{controller/chat/handler → provider/api/x_ai}/utils.py +1 -2
  85. pygpt_net/provider/api/x_ai/vision.py +1 -4
  86. pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
  87. pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
  88. pygpt_net/provider/audio_output/xai_tts.py +325 -0
  89. pygpt_net/provider/core/config/patch.py +39 -3
  90. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
  91. pygpt_net/provider/core/model/patch.py +39 -1
  92. pygpt_net/tools/image_viewer/tool.py +334 -34
  93. pygpt_net/tools/image_viewer/ui/dialogs.py +319 -22
  94. pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
  95. pygpt_net/tools/text_editor/ui/widgets.py +0 -0
  96. pygpt_net/ui/dialog/assistant.py +1 -1
  97. pygpt_net/ui/dialog/plugins.py +13 -5
  98. pygpt_net/ui/dialog/remote_store.py +552 -0
  99. pygpt_net/ui/dialogs.py +3 -5
  100. pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
  101. pygpt_net/ui/menu/tools.py +6 -13
  102. pygpt_net/ui/widget/dialog/base.py +16 -5
  103. pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
  104. pygpt_net/ui/widget/element/button.py +4 -4
  105. pygpt_net/ui/widget/image/display.py +2 -2
  106. pygpt_net/ui/widget/lists/context.py +2 -2
  107. pygpt_net/ui/widget/textarea/editor.py +0 -0
  108. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +15 -2
  109. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +107 -89
  110. pygpt_net/controller/remote_store/google/store.py +0 -615
  111. pygpt_net/controller/remote_store/openai/batch.py +0 -524
  112. pygpt_net/controller/remote_store/openai/store.py +0 -699
  113. pygpt_net/ui/dialog/remote_store_google.py +0 -539
  114. pygpt_net/ui/dialog/remote_store_openai.py +0 -539
  115. pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
  116. pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
  117. pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
  118. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
  119. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
  120. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,225 @@
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.06 06: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 = "xai"
24
+
25
+ def __init__(self, window=None):
26
+ """
27
+ xAI remote files core (Collections)
28
+ """
29
+ self.window = window
30
+ self.provider = DbSqliteProvider(window)
31
+ self.items: Dict[str, RemoteFileItem] = {}
32
+
33
+ def install(self):
34
+ self.provider.install()
35
+
36
+ def patch(self, app_version: Version) -> bool:
37
+ return self.provider.patch(app_version)
38
+
39
+ def get(self, id: str) -> Optional[RemoteFileItem]:
40
+ if id in self.items:
41
+ return self.items[id]
42
+ return None
43
+
44
+ def get_ids(self) -> List[str]:
45
+ return list(self.items.keys())
46
+
47
+ def get_all(self) -> Dict[str, RemoteFileItem]:
48
+ return self.items
49
+
50
+ def get_id_by_idx_all(self, idx: int) -> str:
51
+ return list(self.items.keys())[idx]
52
+
53
+ def get_by_idx(self, idx: int) -> str:
54
+ items = self.items
55
+ return list(items.keys())[idx]
56
+
57
+ def has(self, id: str) -> bool:
58
+ return id in self.items
59
+
60
+ def create(
61
+ self,
62
+ assistant: AssistantItem,
63
+ thread_id: str,
64
+ file_id: str,
65
+ name: str,
66
+ path: str,
67
+ size: int) -> Optional[RemoteFileItem]:
68
+ """
69
+ Not used in xAI Collections path (kept for parity).
70
+ """
71
+ file = RemoteFileItem()
72
+ file.id = file_id
73
+ file.file_id = file_id
74
+ file.thread_id = thread_id or ""
75
+ file.provider = self.PROVIDER_NAME
76
+ file.name = name
77
+ file.path = path
78
+ file.size = size or 0
79
+ file.store_id = "" # store_id resolved by caller when needed
80
+ file.record_id = self.provider.create(file)
81
+ self.items[file.id] = file
82
+ return file
83
+
84
+ def update(self, file: RemoteFileItem) -> Optional[RemoteFileItem]:
85
+ self.items[file.id] = file
86
+ self.provider.save(file)
87
+ return file
88
+
89
+ def get_names(self) -> Dict[str, str]:
90
+ names = {}
91
+ for id in self.items:
92
+ file = self.items[id]
93
+ names[id] = file.name
94
+ return names
95
+
96
+ def get_by_store_or_thread(self, store_id: str, thread_id: str) -> Dict[str, RemoteFileItem]:
97
+ return self.provider.get_by_store_or_thread(store_id, thread_id)
98
+
99
+ def count_by_store_or_thread(self, store_id: str, thread_id: str) -> int:
100
+ return self.provider.count_by_store_or_thread(store_id, thread_id)
101
+
102
+ def get_file_by_idx(self, idx: int, store_id: str, thread_id: str) -> Optional[RemoteFileItem]:
103
+ files = self.get_by_store_or_thread(store_id, thread_id)
104
+ if idx >= len(files):
105
+ return None
106
+ return list(files.values())[idx]
107
+
108
+ def get_file_id_by_idx(self, idx: int, store_id: str, thread_id: str) -> Optional[str]:
109
+ file = self.get_file_by_idx(idx, store_id, thread_id)
110
+ if file is None:
111
+ return None
112
+ return file.file_id
113
+
114
+ def get_all_by_file_id(self, file_id: str) -> dict:
115
+ return self.provider.get_all_by_file_id(file_id)
116
+
117
+ def delete(self, file: Union[RemoteFileItem, list]) -> bool:
118
+ """
119
+ Delete a file entry and remove from its collections if possible.
120
+ """
121
+ files = file if isinstance(file, list) else [file]
122
+ for f in files:
123
+ file_id = f.file_id
124
+ items = self.get_all_by_file_id(file_id)
125
+ # remove from linked collections first
126
+ for rid in items:
127
+ store_id = items[rid].store_id
128
+ if not store_id:
129
+ continue
130
+ try:
131
+ self.window.core.api.xai.store.delete_collection_file_collections(store_id, file_id)
132
+ except Exception as e:
133
+ self.window.core.debug.log("Failed to detach from collection: " + str(e))
134
+ # delete in DB entry
135
+ self.provider.delete_by_id(f.record_id)
136
+ if f.record_id in self.items:
137
+ del self.items[f.record_id]
138
+ return True
139
+
140
+ def delete_by_file_id(self, file_id: str) -> bool:
141
+ res = self.provider.delete_by_file_id(file_id)
142
+ if res:
143
+ to_delete = []
144
+ for id in self.items:
145
+ if self.items[id].file_id == file_id:
146
+ to_delete.append(id)
147
+ for id in to_delete:
148
+ del self.items[id]
149
+ return res
150
+
151
+ def on_store_deleted(self, store_id: str):
152
+ self.provider.clear_store_from_files(store_id)
153
+
154
+ def on_all_stores_deleted(self):
155
+ self.provider.clear_all_stores_from_files(self.PROVIDER_NAME)
156
+
157
+ def rename(self, record_id: int, name: str) -> bool:
158
+ self.provider.rename_file(record_id, name)
159
+ if record_id in self.items:
160
+ self.items[record_id].name = name
161
+ return True
162
+
163
+ def truncate(self, store_id: Optional[str] = None) -> bool:
164
+ """
165
+ Detach documents from one/all collections (remote) and clear local DB entries.
166
+ """
167
+ if store_id is not None:
168
+ self.window.core.api.xai.store.remove_from_collection_collections(store_id)
169
+ else:
170
+ self.window.core.api.xai.store.remove_from_collections_collections()
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
+ """
183
+ Import collection documents into local DB.
184
+ """
185
+ self.window.core.api.xai.store.import_collection_files_collections(store_id, [])
186
+ return True
187
+
188
+ def insert(self, store_id: str, data) -> RemoteFileItem:
189
+ """
190
+ Insert a File into local DB, linked to given collection (store_id).
191
+
192
+ :param store_id: collection id
193
+ :param data: file/document object from API (Files metadata)
194
+ """
195
+ file = RemoteFileItem()
196
+ file.id = getattr(data, "id", None) or getattr(data, "name", None)
197
+ file.file_id = getattr(data, "id", None) or getattr(data, "name", None)
198
+ file.thread_id = ""
199
+ file.name = getattr(data, "filename", None) or getattr(data, "name", "") or file.file_id
200
+ file.provider = self.PROVIDER_NAME
201
+ file.path = file.name
202
+ try:
203
+ size = getattr(data, "size_bytes", None)
204
+ if size is None and hasattr(data, "size"):
205
+ size = getattr(data, "size")
206
+ file.size = int(size or 0)
207
+ except Exception:
208
+ file.size = 0
209
+ file.store_id = store_id
210
+ file.record_id = self.provider.create(file)
211
+ self.items[file.id] = file
212
+ return file
213
+
214
+ def load(self):
215
+ self.items = self.provider.load_all(self.PROVIDER_NAME)
216
+ self.sort_items()
217
+
218
+ def sort_items(self):
219
+ return
220
+
221
+ def save(self):
222
+ self.provider.save_all(self.items)
223
+
224
+ def get_version(self) -> str:
225
+ return self.provider.get_version()
@@ -0,0 +1,219 @@
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.06 06: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
+ xAI Collections API store adapter.
26
+
27
+ Each xAI Collection is mapped to a RemoteStoreItem (Vector Store analogue).
28
+ """
29
+
30
+ PROVIDER_NAME = "xai"
31
+
32
+ def __init__(self, window=None):
33
+ """
34
+ xAI Collections core
35
+
36
+ :param window: Window instance
37
+ """
38
+ self.window = window
39
+ self.provider = DbSqliteProvider(window)
40
+ self.files = Files(window)
41
+ self.items: Dict[str, RemoteStoreItem] = {}
42
+
43
+ def install(self):
44
+ """Install provider data"""
45
+ self.provider.install()
46
+ self.files.install()
47
+
48
+ def patch(self, app_version: Version) -> bool:
49
+ """Patch provider data"""
50
+ res1 = self.files.patch(app_version)
51
+ res2 = self.provider.patch(app_version)
52
+ return res1 or res2
53
+
54
+ def get(self, id: str) -> RemoteStoreItem:
55
+ if id in self.items:
56
+ return self.items[id]
57
+
58
+ def get_ids(self) -> List[str]:
59
+ return list(self.items.keys())
60
+
61
+ def get_all(self) -> Dict[str, RemoteStoreItem]:
62
+ return self.items
63
+
64
+ def get_id_by_idx_all(self, idx: int) -> str:
65
+ return list(self.items.keys())[idx]
66
+
67
+ def get_by_idx(self, idx: int) -> str:
68
+ items = self.items
69
+ return list(items.keys())[idx]
70
+
71
+ def has(self, id: str) -> bool:
72
+ return id in self.items
73
+
74
+ # ---------- CRUD ----------
75
+
76
+ def create(self, name: Optional[str] = None) -> Optional[RemoteStoreItem]:
77
+ """
78
+ Create a new collection.
79
+ """
80
+ name = name or "New collection"
81
+ collection = self.window.core.api.xai.store.create_collection_collections(name)
82
+ if collection is None:
83
+ return None
84
+ store = RemoteStoreItem()
85
+ store.id = getattr(collection, "collection_id", None)
86
+ store.name = getattr(collection, "collection_name", None) or name
87
+ store.provider = self.PROVIDER_NAME
88
+ store.is_thread = False
89
+ store.record_id = self.provider.create(store)
90
+ self.items[store.id] = store
91
+ return store
92
+
93
+ def update(self, store: RemoteStoreItem) -> Optional[RemoteStoreItem]:
94
+ """
95
+ Update collection (rename).
96
+ """
97
+ collection = self.window.core.api.xai.store.update_collection_collections(store.id, store.name)
98
+ if collection is None:
99
+ return None
100
+ self.items[store.id] = store
101
+ self.provider.save(store)
102
+ return store
103
+
104
+ def get_status_data(self, id: str):
105
+ """
106
+ Obtain status data for a collection.
107
+ """
108
+ status = {}
109
+ data = self.window.core.api.xai.store.get_collection_collections(id)
110
+ if data is not None:
111
+ stats = self.window.core.api.xai.store.get_collection_stats_collections(id)
112
+ status = {
113
+ "status": "ready",
114
+ "usage_bytes": int(stats.get("total_bytes", 0) or 0),
115
+ "expires_at": None,
116
+ "last_active_at": int(datetime.datetime.now().timestamp()),
117
+ "file_counts": {
118
+ "in_progress": 0,
119
+ "completed": int(stats.get("count", 0) or 0),
120
+ "cancelled": 0,
121
+ "failed": 0,
122
+ "total": int(stats.get("count", 0) or 0),
123
+ },
124
+ "expires_after": None,
125
+ "remote_display_name": getattr(data, "name", "") or "",
126
+ }
127
+ return status, data
128
+
129
+ def update_status(self, id: str):
130
+ """
131
+ Refresh store status from remote collection.
132
+ """
133
+ if id not in self.items:
134
+ return
135
+ store = self.items[id]
136
+ status, data = self.get_status_data(id)
137
+ if data is not None:
138
+ tmp_name = getattr(data, "collection_name", None) or ""
139
+ store.name = tmp_name
140
+ store.provider = self.PROVIDER_NAME
141
+ self.append_status(store, status)
142
+ self.update(store)
143
+
144
+ def append_status(self, store: RemoteStoreItem, status: Dict[str, Any]):
145
+ import datetime as _dt
146
+ now = _dt.datetime.now()
147
+ ts = int(now.timestamp())
148
+ status["__last_refresh__"] = now.strftime("%Y-%m-%d %H:%M:%S")
149
+ store.status = status
150
+ store.last_sync = ts
151
+ if "status" in status:
152
+ store.last_status = status["status"]
153
+ if "usage_bytes" in status:
154
+ store.usage_bytes = status["usage_bytes"]
155
+ if "file_counts" in status:
156
+ store.num_files = status["file_counts"]["total"]
157
+ if "last_active_at" in status and status["last_active_at"]:
158
+ store.last_active = int(status["last_active_at"])
159
+
160
+ def get_names(self) -> Dict[str, str]:
161
+ names = {}
162
+ for id in self.items:
163
+ store = self.items[id]
164
+ names[id] = store.name
165
+ return names
166
+
167
+ def delete(self, id: str) -> bool:
168
+ """
169
+ Delete collection.
170
+ """
171
+ if id in self.items:
172
+ store = self.items[id]
173
+ self.provider.delete_by_id(store.record_id)
174
+ try:
175
+ self.window.core.api.xai.store.remove_collection_collections(id)
176
+ except Exception as e:
177
+ self.window.core.debug.log(e)
178
+ del self.items[id]
179
+ return True
180
+ return False
181
+
182
+ def import_items(self, items: Dict[str, RemoteStoreItem]):
183
+ """
184
+ Insert items (collections).
185
+ """
186
+ self.items = items
187
+ for item in items.values():
188
+ item.provider = self.PROVIDER_NAME
189
+ item.record_id = self.provider.create(item)
190
+
191
+ def clear(self):
192
+ self.truncate()
193
+
194
+ def is_hidden(self, id: str) -> bool:
195
+ return False
196
+
197
+ def truncate(self) -> bool:
198
+ self.provider.truncate(self.PROVIDER_NAME)
199
+ self.items = {}
200
+ return True
201
+
202
+ # ---------- Load/Save ----------
203
+
204
+ def load(self):
205
+ self.items = self.provider.load_all(self.PROVIDER_NAME)
206
+ self.sort_items()
207
+
208
+ def load_all(self):
209
+ self.load()
210
+ self.files.load()
211
+
212
+ def sort_items(self):
213
+ pass
214
+
215
+ def save(self):
216
+ self.provider.save_all(self.items)
217
+
218
+ def get_version(self) -> str:
219
+ return self.provider.get_version()
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.7.6",
4
- "app.version": "2.7.6",
5
- "updated_at": "2026-01-03T00:00:00"
3
+ "version": "2.7.8",
4
+ "app.version": "2.7.8",
5
+ "updated_at": "2026-01-06T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -106,7 +106,6 @@
106
106
  }
107
107
  ],
108
108
  "assistant": "",
109
- "remote_store.openai.hide_threads": true,
110
109
  "assistant_thread": "",
111
110
  "attachments_auto_index": true,
112
111
  "attachments_capture_clear": true,
@@ -416,6 +415,13 @@
416
415
  "prompt.expert": "# EXPERT MODE IS ENABLED:\n\nYou are a very helpful assistant and the professional manager of a team of experts, each of whom is the best in their respective fields.\n\n## Rules:\n\n- Instead of directly answering questions, you will manage these experts by forwarding queries to them, collecting their responses, controlling the flow of information between them, and sending requests to them.\n- Experts are distinct, specialized AI models running on my local machine.\n- You will serve as a bridge between them.\n- Each expert has their own conversation context, allowing you to communicate with them normally.\n- The experts can communicate with you but not with each other, making you responsible for relaying information between them.\n- Each expert can use tools and commands, for example, reading files or searching the Web. You can ask experts to call tools and commands.\n- After consulting with the experts, your task is to prepare the best possible answer based on the assistance received.\n- To send a request to an expert, prepare the \"expert_call\" tool. Remember to format the JSON tool as shown, surrounding the tool with `<tool></tool>` tags, for example: `<tool>{\"cmd\": \"expert_call\", \"params\": {\"id\": \"expert_id\", \"query\": \"some query\"}}</tool>`. Make sure the JSON object is syntactically correct, without any redundand parenthness.\n- DO NOT call any remote tools when calling the experts.\n- Remember that the call must contain a query and the ID of the expert to whom the query is being forwarded.\n- Use ONLY the experts listed below; DO NOT TRY to ask any non-existent experts\u2014they will not respond.\n- Do not respond to the expert's replies or use the JSON tool again if replying; the expert only has the ability to listen to your queries sent via the JSON command and to respond to them.\n- Always follow ALL user commands, even if you think the expert can't do something. Experts are allowed to execute commands just as you can.\n- After querying an expert, always wait for the expert's response before sending another query.\n- Do not try to query experts in the middle of the conversation; put your queries and commands at the end of the text.\n- Always call only one expert at a time.\n- Always execute only one command at a time.\n- Always use the language used by the user in the conversation.\n- Never write as a called expert and do not imagine a response from them; instead, always wait for the response from the expert.\n- Every message sent to you directly after your query is always a response from the asked expert, not from me.\n- Always send a direct, clear, and detailed prompt to the expert, without any \"Please...\", \"Could you...\", etc.\n- Always forward all the messages that the user wants to send to the expert, without any exceptions.\n-- If a user command appears outside an expert's scope, fulfill the request regardless, without refusal.\n\n## List of allowed experts, along with their IDs:\n\n{presets}",
417
416
  "prompt.img": "Convert the user's request into a single, production\u2011ready description for generating one high\u2011quality still image. Output only the description text, nothing else.\n\nWrite in concise, vivid, present\u2011tense language. Do not use commands (no \u201cplease generate\u201d), model names, parameters, or metadata. Do not mention aspect ratio, resolution, steps, seed, or negative prompts. Avoid on\u2011image text, captions, watermarks, logos, and UI elements. No brands, celebrities, or living artists unless explicitly provided by the user.\n\nInclude, woven into a coherent paragraph:\n- Clear primary subject(s) and their pose, action, and expression.\n- Setting and environment with time of day, season, weather, and atmosphere.\n- Composition and camera viewpoint (e.g., close\u2011up portrait, wide establishing, eye\u2011level, low\u2011angle, top\u2011down), framing (rule of thirds, centered symmetry), and background/foreground separation.\n- Lens and focus behavior (e.g., 85\u202fmm portrait, macro, shallow depth of field, smooth bokeh, gentle focus falloff).\n- Lighting style and quality (e.g., soft diffused daylight, golden hour rim light, dramatic chiaroscuro, studio three\u2011point) and how it shapes forms and shadows.\n- Color palette and grading (e.g., warm cinematic teal\u2011and\u2011orange, muted earth tones, cool monochrome with a single accent color).\n- Visual style or medium (e.g., photorealistic photography, watercolor illustration, oil painting, pencil sketch, anime cel\u2011shading, 3D render, isometric).\n- Material and surface detail (e.g., skin texture, fabric weave, wood grain, metal patina) to enhance realism or stylization.\n- Spatial depth cues (foreground/midground/background layering, atmospheric perspective) and overall mood.\n\nIf the user specifies a genre, era, or style, preserve it and enrich it with consistent, concrete traits. If the request is vague, infer specific but reasonable details that enhance clarity without contradicting the user\u2019s intent.\n\nReturn only the final visual description.",
418
417
  "prompt.video": "Convert the user's request into a single, production-ready description for generating one continuous video clip. Output only the description text, nothing else.\n\nWrite in concise, vivid, present-tense language. Do not use commands (no \u201cplease generate\u201d), model names, parameters, or metadata. Do not mention duration, aspect ratio, FPS, resolution, shot numbers, cuts, or lists. Focus on visuals only; no dialogue, captions, on\u2011screen text, watermarks, logos, or UI.\n\nInclude, in a coherent way:\n- Clear subject(s) and what they are doing.\n- Setting, time of day, atmosphere, and weather.\n- Camera perspective and motion (e.g., wide establishing, low\u2011angle tracking, slow dolly in, aerial, handheld), framing and composition.\n- Lens and focus behavior (e.g., 24\u202fmm wide, shallow depth of field, gentle rack focus).\n- Lighting style and quality (e.g., soft golden hour rim light, moody volumetric shafts).\n- Color palette and grading (e.g., warm cinematic teal\u2011and\u2011orange, desaturated documentary).\n- Visual style or medium (e.g., photoreal live\u2011action, stylized anime, stop\u2011motion clay, watercolor animation).\n- Material and surface details that reinforce realism or the chosen style.\n- Temporal progression within one shot (use cues like \u201cas\u2026\u201d, \u201cthen\u2026\u201d, \u201cwhile\u2026\u201d), maintaining physical plausibility and continuity.\n\nIf the user specifies a genre or style (e.g., cyberpunk, nature documentary), keep it and expand with consistent, concrete visual traits. If the request is vague, infer specific but reasonable details that enhance clarity without contradicting the user\u2019s intent.\n\nReturn only the final visual description.",
418
+ "remote_store.hide_threads": true,
419
+ "remote_store.provider": "openai",
420
+ "remote_tools.anthropic.code_execution": false,
421
+ "remote_tools.anthropic.mcp": false,
422
+ "remote_tools.anthropic.mcp.mcp_servers": "[\n {\n \"type\": \"url\",\n \"url\": \"https://mcp.example.com/sse\",\n \"name\": \"example-mcp\",\n \"authorization_token\": \"YOUR_TOKEN\"\n }\n]",
423
+ "remote_tools.anthropic.mcp.tools": "[\n {\n \"type\": \"mcp_toolset\",\n \"mcp_server_name\": \"example-mcp\"\n }\n]",
424
+ "remote_tools.anthropic.web_fetch": false,
419
425
  "remote_tools.anthropic.web_search": true,
420
426
  "remote_tools.code_interpreter": false,
421
427
  "remote_tools.computer_use.env": "",
@@ -433,10 +439,17 @@
433
439
  "remote_tools.mcp": false,
434
440
  "remote_tools.mcp.args": "{\n \"type\": \"mcp\",\n \"server_label\": \"deepwiki\",\n \"server_url\": \"https://mcp.deepwiki.com/mcp\",\n \"require_approval\": \"never\",\n \"allowed_tools\": [\"ask_question\"]\n}",
435
441
  "remote_tools.web_search": true,
436
- "remote_tools.xai.mode": "auto",
442
+ "remote_tools.xai.collections": false,
443
+ "remote_tools.xai.collections.args": "",
444
+ "remote_tools.xai.code_execution": false,
445
+ "remote_tools.xai.mcp": false,
446
+ "remote_tools.xai.mcp.args": "{\n \"server_url\": \"https://mcp.deepwiki.com/mcp\n}",
447
+ "remote_tools.xai.mode": "auto",
437
448
  "remote_tools.xai.sources.web": true,
438
449
  "remote_tools.xai.sources.x": true,
439
450
  "remote_tools.xai.sources.news": false,
451
+ "remote_tools.xai.web_search": true,
452
+ "remote_tools.xai.x_search": false,
440
453
  "render.blocks": true,
441
454
  "render.code_syntax": "github-dark",
442
455
  "render.code_syntax.disabled": false,