pygpt-net 2.7.3__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.
- pygpt_net/CHANGELOG.txt +15 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +382 -350
- 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/attachment.py +5 -1
- pygpt_net/controller/chat/handler/google_stream.py +307 -1
- pygpt_net/controller/chat/handler/worker.py +8 -1
- pygpt_net/controller/chat/image.py +15 -3
- pygpt_net/controller/dialogs/confirm.py +73 -101
- pygpt_net/controller/files/files.py +3 -1
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/layout/layout.py +2 -2
- 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/theme/nodes.py +2 -1
- pygpt_net/controller/ui/mode.py +5 -1
- pygpt_net/controller/ui/ui.py +36 -2
- 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/helpers.py +5 -0
- pygpt_net/data/config/config.json +8 -5
- pygpt_net/data/config/models.json +77 -3
- pygpt_net/data/config/settings.json +45 -14
- pygpt_net/data/css/web-blocks.css +3 -0
- pygpt_net/data/css/web-chatgpt.css +3 -0
- pygpt_net/data/locale/locale.de.ini +43 -41
- pygpt_net/data/locale/locale.en.ini +56 -44
- pygpt_net/data/locale/locale.es.ini +43 -41
- pygpt_net/data/locale/locale.fr.ini +43 -41
- pygpt_net/data/locale/locale.it.ini +43 -41
- pygpt_net/data/locale/locale.pl.ini +43 -41
- pygpt_net/data/locale/locale.uk.ini +43 -41
- pygpt_net/data/locale/locale.zh.ini +43 -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 -1
- pygpt_net/item/store.py +238 -0
- pygpt_net/launcher.py +115 -55
- pygpt_net/migrations/Version20260102190000.py +35 -0
- pygpt_net/migrations/__init__.py +3 -1
- pygpt_net/plugin/cmd_mouse_control/config.py +470 -1
- pygpt_net/plugin/cmd_mouse_control/plugin.py +488 -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/preload.py +243 -0
- pygpt_net/provider/api/google/__init__.py +16 -54
- pygpt_net/provider/api/google/chat.py +546 -129
- pygpt_net/provider/api/google/computer.py +190 -0
- pygpt_net/provider/api/google/image.py +74 -6
- 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/video.py +9 -4
- 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/computer.py +10 -1
- pygpt_net/provider/api/openai/image.py +42 -19
- pygpt_net/provider/api/openai/store.py +6 -6
- pygpt_net/provider/api/openai/video.py +27 -2
- pygpt_net/provider/api/openai/worker/importer.py +24 -24
- pygpt_net/provider/api/x_ai/image.py +25 -2
- pygpt_net/provider/core/config/patch.py +23 -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/ui/base/config_dialog.py +3 -2
- 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/chat/input.py +20 -2
- pygpt_net/ui/layout/chat/painter.py +6 -4
- pygpt_net/ui/layout/toolbox/computer_env.py +26 -8
- pygpt_net/ui/layout/toolbox/image.py +5 -5
- pygpt_net/ui/layout/toolbox/video.py +5 -4
- pygpt_net/ui/main.py +84 -3
- pygpt_net/ui/menu/tools.py +13 -5
- pygpt_net/ui/widget/dialog/base.py +3 -10
- 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/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 +158 -4
- pygpt_net/ui/widget/textarea/input_extra.py +664 -0
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.5.dist-info}/METADATA +48 -9
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.5.dist-info}/RECORD +157 -130
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.5.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.5.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.3.dist-info → pygpt_net-2.7.5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
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 os
|
|
13
|
+
|
|
14
|
+
from PySide6.QtCore import QObject, Signal, QRunnable, Slot
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Importer(QObject):
|
|
18
|
+
def __init__(self, window=None):
|
|
19
|
+
"""
|
|
20
|
+
Importer core (Google File Search)
|
|
21
|
+
|
|
22
|
+
:param window: Window instance
|
|
23
|
+
"""
|
|
24
|
+
super(Importer, self).__init__()
|
|
25
|
+
self.window = window
|
|
26
|
+
self.worker = None
|
|
27
|
+
|
|
28
|
+
@Slot(str, object)
|
|
29
|
+
def handle_error(self, mode: str, err: any):
|
|
30
|
+
"""
|
|
31
|
+
Handle thread error signal
|
|
32
|
+
|
|
33
|
+
:param mode: mode
|
|
34
|
+
:param err: error message
|
|
35
|
+
"""
|
|
36
|
+
if mode == "import_files":
|
|
37
|
+
self.window.controller.remote_store.google.batch.handle_imported_files_failed(err)
|
|
38
|
+
elif mode == "truncate_files":
|
|
39
|
+
self.window.controller.remote_store.google.batch.handle_truncated_files_failed(err)
|
|
40
|
+
elif mode == "upload_files":
|
|
41
|
+
self.window.controller.remote_store.google.batch.handle_uploaded_files_failed(err)
|
|
42
|
+
elif mode in "vector_stores":
|
|
43
|
+
self.window.controller.remote_store.google.batch.handle_imported_stores_failed(err)
|
|
44
|
+
elif mode in "truncate_vector_stores":
|
|
45
|
+
self.window.controller.remote_store.google.batch.handle_truncated_stores_failed(err)
|
|
46
|
+
elif mode in "refresh_vector_stores":
|
|
47
|
+
self.window.controller.remote_store.google.batch.handle_refreshed_stores_failed(err)
|
|
48
|
+
|
|
49
|
+
@Slot(str, str, int)
|
|
50
|
+
def handle_finished(self, mode: str, store_id: str = None, num: int = 0):
|
|
51
|
+
"""
|
|
52
|
+
Handle thread finished signal
|
|
53
|
+
|
|
54
|
+
:param mode: mode
|
|
55
|
+
:param store_id: store ID
|
|
56
|
+
:param num: number of affected items
|
|
57
|
+
"""
|
|
58
|
+
if mode == "import_files":
|
|
59
|
+
self.window.controller.remote_store.google.batch.handle_imported_files(num)
|
|
60
|
+
elif mode == "truncate_files":
|
|
61
|
+
self.window.controller.remote_store.google.batch.handle_truncated_files(store_id, num)
|
|
62
|
+
elif mode == "upload_files":
|
|
63
|
+
self.window.controller.remote_store.google.batch.handle_uploaded_files(num)
|
|
64
|
+
elif mode == "vector_stores":
|
|
65
|
+
self.window.controller.remote_store.google.batch.handle_imported_stores(num)
|
|
66
|
+
elif mode == "truncate_vector_stores":
|
|
67
|
+
self.window.controller.remote_store.google.batch.handle_truncated_stores(num)
|
|
68
|
+
elif mode == "refresh_vector_stores":
|
|
69
|
+
self.window.controller.remote_store.google.batch.handle_refreshed_stores(num)
|
|
70
|
+
|
|
71
|
+
@Slot(str, str)
|
|
72
|
+
def handle_status(self, mode: str, msg: str):
|
|
73
|
+
"""
|
|
74
|
+
Handle thread status change signal
|
|
75
|
+
|
|
76
|
+
:param mode: mode
|
|
77
|
+
:param msg: message
|
|
78
|
+
"""
|
|
79
|
+
self.window.controller.assistant.batch.handle_status_change(mode, msg)
|
|
80
|
+
|
|
81
|
+
@Slot(str, str)
|
|
82
|
+
def handle_log(self, mode: str, msg: str):
|
|
83
|
+
"""
|
|
84
|
+
Handle thread log message signal
|
|
85
|
+
|
|
86
|
+
:param mode: mode
|
|
87
|
+
:param msg: message
|
|
88
|
+
"""
|
|
89
|
+
self.window.controller.assistant.threads.log(mode + ": " + msg)
|
|
90
|
+
|
|
91
|
+
def import_assistants(self):
|
|
92
|
+
"""Import assistants (kept for parity; no-op for Google if unused)"""
|
|
93
|
+
self.worker = ImportWorker()
|
|
94
|
+
self.worker.window = self.window
|
|
95
|
+
self.worker.mode = "assistants"
|
|
96
|
+
self.connect_signals(self.worker)
|
|
97
|
+
self.window.threadpool.start(self.worker)
|
|
98
|
+
|
|
99
|
+
def import_vector_stores(self):
|
|
100
|
+
"""Import File Search stores"""
|
|
101
|
+
self.worker = ImportWorker()
|
|
102
|
+
self.worker.window = self.window
|
|
103
|
+
self.worker.mode = "vector_stores"
|
|
104
|
+
self.connect_signals(self.worker)
|
|
105
|
+
self.window.threadpool.start(self.worker)
|
|
106
|
+
|
|
107
|
+
def truncate_vector_stores(self):
|
|
108
|
+
"""Truncate File Search stores"""
|
|
109
|
+
self.worker = ImportWorker()
|
|
110
|
+
self.worker.window = self.window
|
|
111
|
+
self.worker.mode = "truncate_vector_stores"
|
|
112
|
+
self.connect_signals(self.worker)
|
|
113
|
+
self.window.threadpool.start(self.worker)
|
|
114
|
+
|
|
115
|
+
def truncate_files(self, store_id: str = None):
|
|
116
|
+
"""
|
|
117
|
+
Truncate documents
|
|
118
|
+
|
|
119
|
+
:param store_id: store name ('fileSearchStores/...').
|
|
120
|
+
"""
|
|
121
|
+
self.worker = ImportWorker()
|
|
122
|
+
self.worker.window = self.window
|
|
123
|
+
self.worker.mode = "truncate_files"
|
|
124
|
+
self.worker.store_id = store_id
|
|
125
|
+
self.connect_signals(self.worker)
|
|
126
|
+
self.window.threadpool.start(self.worker)
|
|
127
|
+
|
|
128
|
+
def upload_files(self, store_id: str, files: list = None):
|
|
129
|
+
"""
|
|
130
|
+
Upload files
|
|
131
|
+
|
|
132
|
+
:param store_id: store name ('fileSearchStores/...').
|
|
133
|
+
:param files: list of file paths
|
|
134
|
+
"""
|
|
135
|
+
print("Uploading files: {}".format(files))
|
|
136
|
+
print("Store ID: {}".format(store_id))
|
|
137
|
+
self.worker = ImportWorker()
|
|
138
|
+
self.worker.window = self.window
|
|
139
|
+
self.worker.mode = "upload_files"
|
|
140
|
+
self.worker.store_id = store_id
|
|
141
|
+
self.worker.files = files or []
|
|
142
|
+
self.connect_signals(self.worker)
|
|
143
|
+
self.window.threadpool.start(self.worker)
|
|
144
|
+
|
|
145
|
+
def refresh_vector_stores(self):
|
|
146
|
+
"""Refresh File Search stores"""
|
|
147
|
+
self.worker = ImportWorker()
|
|
148
|
+
self.worker.window = self.window
|
|
149
|
+
self.worker.mode = "refresh_vector_stores"
|
|
150
|
+
self.connect_signals(self.worker)
|
|
151
|
+
self.window.threadpool.start(self.worker)
|
|
152
|
+
|
|
153
|
+
def import_files(self, store_id: str = None):
|
|
154
|
+
"""
|
|
155
|
+
Import File Search documents
|
|
156
|
+
|
|
157
|
+
:param store_id: store name ('fileSearchStores/...').
|
|
158
|
+
"""
|
|
159
|
+
self.worker = ImportWorker()
|
|
160
|
+
self.worker.window = self.window
|
|
161
|
+
self.worker.mode = "import_files"
|
|
162
|
+
self.worker.store_id = store_id
|
|
163
|
+
self.connect_signals(self.worker)
|
|
164
|
+
self.window.threadpool.start(self.worker)
|
|
165
|
+
|
|
166
|
+
def connect_signals(self, worker):
|
|
167
|
+
"""
|
|
168
|
+
Connect signals
|
|
169
|
+
|
|
170
|
+
:param worker: worker instance
|
|
171
|
+
"""
|
|
172
|
+
worker.signals.finished.connect(self.handle_finished)
|
|
173
|
+
worker.signals.error.connect(self.handle_error)
|
|
174
|
+
worker.signals.status.connect(self.handle_status)
|
|
175
|
+
worker.signals.log.connect(self.handle_log)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ImportWorkerSignals(QObject):
|
|
179
|
+
"""Import worker signals"""
|
|
180
|
+
status = Signal(str, str) # mode, message
|
|
181
|
+
finished = Signal(str, str, int) # mode, store_id, num
|
|
182
|
+
error = Signal(str, object) # mode, error
|
|
183
|
+
log = Signal(str, str) # mode, message
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ImportWorker(QRunnable):
|
|
187
|
+
"""Import worker (Google)"""
|
|
188
|
+
def __init__(self, *args, **kwargs):
|
|
189
|
+
super().__init__()
|
|
190
|
+
self.signals = ImportWorkerSignals()
|
|
191
|
+
self.window = None
|
|
192
|
+
self.mode = "assistants"
|
|
193
|
+
self.assistant = None
|
|
194
|
+
self.store_id = None
|
|
195
|
+
self.files = []
|
|
196
|
+
|
|
197
|
+
@Slot()
|
|
198
|
+
def run(self):
|
|
199
|
+
"""Importer thread"""
|
|
200
|
+
try:
|
|
201
|
+
if self.mode == "vector_stores":
|
|
202
|
+
if self.import_vector_stores():
|
|
203
|
+
self.import_files()
|
|
204
|
+
elif self.mode == "truncate_vector_stores":
|
|
205
|
+
self.truncate_vector_stores()
|
|
206
|
+
elif self.mode == "refresh_vector_stores":
|
|
207
|
+
self.refresh_vector_stores()
|
|
208
|
+
elif self.mode == "truncate_files":
|
|
209
|
+
self.truncate_files()
|
|
210
|
+
elif self.mode == "import_files":
|
|
211
|
+
self.import_files()
|
|
212
|
+
elif self.mode == "upload_files":
|
|
213
|
+
self.upload_files()
|
|
214
|
+
except Exception as e:
|
|
215
|
+
self.signals.error.emit(self.mode, e)
|
|
216
|
+
finally:
|
|
217
|
+
self.cleanup()
|
|
218
|
+
|
|
219
|
+
def import_assistants(self, silent: bool = False) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Import assistants (not used for Google by default; kept for parity)
|
|
222
|
+
|
|
223
|
+
:param silent: silent mode
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
if not silent:
|
|
227
|
+
self.signals.finished.emit("assistants", self.store_id, 0)
|
|
228
|
+
return True
|
|
229
|
+
except Exception as e:
|
|
230
|
+
self.signals.error.emit("assistants", e)
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def import_vector_stores(self, silent: bool = False) -> bool:
|
|
234
|
+
"""
|
|
235
|
+
Import File Search stores
|
|
236
|
+
|
|
237
|
+
:param silent: silent mode (no signals emit)
|
|
238
|
+
"""
|
|
239
|
+
try:
|
|
240
|
+
self.log("Importing File Search stores...")
|
|
241
|
+
self.window.core.remote_store.google.clear()
|
|
242
|
+
items = {}
|
|
243
|
+
self.window.core.api.google.store.import_stores(items, callback=self.callback)
|
|
244
|
+
self.window.core.remote_store.google.import_items(items)
|
|
245
|
+
if not silent:
|
|
246
|
+
self.signals.finished.emit("vector_stores", self.store_id, len(items))
|
|
247
|
+
return True
|
|
248
|
+
except Exception as e:
|
|
249
|
+
self.log("API error: {}".format(e))
|
|
250
|
+
self.signals.error.emit("vector_stores", e)
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
def truncate_vector_stores(self, silent: bool = False) -> bool:
|
|
254
|
+
"""
|
|
255
|
+
Truncate all File Search stores in API
|
|
256
|
+
|
|
257
|
+
:param silent: silent mode
|
|
258
|
+
"""
|
|
259
|
+
try:
|
|
260
|
+
self.log("Truncating stores...")
|
|
261
|
+
num = self.window.core.api.google.store.remove_all(callback=self.callback)
|
|
262
|
+
self.window.core.remote_store.google.items = {}
|
|
263
|
+
self.window.core.remote_store.google.save()
|
|
264
|
+
if not silent:
|
|
265
|
+
self.signals.finished.emit("truncate_vector_stores", self.store_id, num)
|
|
266
|
+
return True
|
|
267
|
+
except Exception as e:
|
|
268
|
+
self.log("API error: {}".format(e))
|
|
269
|
+
self.signals.error.emit("truncate_vector_stores", e)
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
def refresh_vector_stores(self, silent: bool = False) -> bool:
|
|
273
|
+
"""
|
|
274
|
+
Refresh all File Search stores in API
|
|
275
|
+
|
|
276
|
+
:param silent: silent mode
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
self.log("Refreshing stores...")
|
|
280
|
+
num = 0
|
|
281
|
+
stores = self.window.core.remote_store.google.items
|
|
282
|
+
for id in stores:
|
|
283
|
+
store = stores[id]
|
|
284
|
+
try:
|
|
285
|
+
self.window.controller.remote_store.google.refresh_store(store, update=False)
|
|
286
|
+
num += 1
|
|
287
|
+
except Exception as e:
|
|
288
|
+
self.log("Failed to refresh store: {}".format(id))
|
|
289
|
+
self.window.core.debug.log(e)
|
|
290
|
+
if not silent:
|
|
291
|
+
self.signals.finished.emit("refresh_vector_stores", self.store_id, num)
|
|
292
|
+
return True
|
|
293
|
+
except Exception as e:
|
|
294
|
+
self.log("API error: {}".format(e))
|
|
295
|
+
self.signals.error.emit("refresh_vector_stores", e)
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
def truncate_files(self, silent: bool = False) -> bool:
|
|
299
|
+
"""
|
|
300
|
+
Truncate documents in API
|
|
301
|
+
|
|
302
|
+
:param silent: silent mode
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
if self.store_id is None:
|
|
306
|
+
self.log("Truncating all documents in all stores...")
|
|
307
|
+
self.window.core.remote_store.google.files.truncate() # clear all locally and remote
|
|
308
|
+
num = self.window.core.api.google.store.remove_from_stores()
|
|
309
|
+
else:
|
|
310
|
+
self.log("Truncating documents for store: {}".format(self.store_id))
|
|
311
|
+
self.window.core.remote_store.google.files.truncate(self.store_id)
|
|
312
|
+
num = self.window.core.api.google.store.remove_from_store(self.store_id)
|
|
313
|
+
if not silent:
|
|
314
|
+
self.signals.finished.emit("truncate_files", self.store_id, num)
|
|
315
|
+
return True
|
|
316
|
+
except Exception as e:
|
|
317
|
+
self.log("API error: {}".format(e))
|
|
318
|
+
self.signals.error.emit("truncate_files", e)
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
def upload_files(self, silent: bool = False) -> bool:
|
|
322
|
+
"""
|
|
323
|
+
Upload files directly to a File Search store (creates Documents)
|
|
324
|
+
|
|
325
|
+
:param silent: silent mode
|
|
326
|
+
"""
|
|
327
|
+
num = 0
|
|
328
|
+
try:
|
|
329
|
+
self.log("Uploading files to File Search store...")
|
|
330
|
+
for file in self.files:
|
|
331
|
+
try:
|
|
332
|
+
doc = self.window.core.api.google.store.upload_to_store(self.store_id, file)
|
|
333
|
+
if doc is not None:
|
|
334
|
+
self.window.core.remote_store.google.files.insert(self.store_id, doc)
|
|
335
|
+
msg = "Uploaded file: {}/{}".format((num + 1), len(self.files))
|
|
336
|
+
self.signals.status.emit("upload_files", msg)
|
|
337
|
+
self.log(msg)
|
|
338
|
+
num += 1
|
|
339
|
+
else:
|
|
340
|
+
self.signals.status.emit("upload_files", "Failed to upload file: {}".format(os.path.basename(file)))
|
|
341
|
+
except Exception as e:
|
|
342
|
+
self.window.core.debug.log(e)
|
|
343
|
+
self.signals.status.emit("upload_files", "Failed to upload file: {}".format(os.path.basename(file)))
|
|
344
|
+
if not silent:
|
|
345
|
+
self.signals.finished.emit("upload_files", self.store_id, num)
|
|
346
|
+
return True
|
|
347
|
+
except Exception as e:
|
|
348
|
+
self.log("API error: {}".format(e))
|
|
349
|
+
self.signals.error.emit("upload_files", e)
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
def import_files(self, silent: bool = False) -> bool:
|
|
353
|
+
"""
|
|
354
|
+
Import documents from API
|
|
355
|
+
|
|
356
|
+
:param silent: silent mode
|
|
357
|
+
"""
|
|
358
|
+
try:
|
|
359
|
+
if self.store_id is None:
|
|
360
|
+
self.log("Importing all documents...")
|
|
361
|
+
self.window.core.remote_store.google.files.truncate_local() # clear local DB (all)
|
|
362
|
+
num = self.window.core.api.google.store.import_stores_files(self.callback) # import all
|
|
363
|
+
else:
|
|
364
|
+
self.log("Importing documents for store: {}".format(self.store_id))
|
|
365
|
+
self.window.core.remote_store.google.files.truncate_local(self.store_id)
|
|
366
|
+
items = self.window.core.api.google.store.import_store_files(self.store_id, [], callback=self.callback)
|
|
367
|
+
num = len(items)
|
|
368
|
+
if not silent:
|
|
369
|
+
self.signals.finished.emit("import_files", self.store_id, num)
|
|
370
|
+
return True
|
|
371
|
+
except Exception as e:
|
|
372
|
+
self.log("API error: {}".format(e))
|
|
373
|
+
self.signals.error.emit("import_files", e)
|
|
374
|
+
return False
|
|
375
|
+
|
|
376
|
+
def callback(self, msg: str):
|
|
377
|
+
"""Log callback"""
|
|
378
|
+
self.log(msg)
|
|
379
|
+
|
|
380
|
+
def log(self, msg: str):
|
|
381
|
+
"""Log message"""
|
|
382
|
+
self.signals.log.emit(self.mode, msg)
|
|
383
|
+
|
|
384
|
+
def cleanup(self):
|
|
385
|
+
"""Cleanup resources after worker execution."""
|
|
386
|
+
sig = self.signals
|
|
387
|
+
self.signals = None
|
|
388
|
+
if sig is not None:
|
|
389
|
+
try:
|
|
390
|
+
sig.deleteLater()
|
|
391
|
+
except RuntimeError:
|
|
392
|
+
pass
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2026.01.02 02:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -39,11 +39,20 @@ class Computer:
|
|
|
39
39
|
Get Computer use tool
|
|
40
40
|
:return: dict
|
|
41
41
|
"""
|
|
42
|
+
is_sandbox = bool(self.window.core.config.get("remote_tools.computer_use.sandbox", False))
|
|
42
43
|
env = self.get_current_env()
|
|
43
44
|
screen = self.window.app.primaryScreen()
|
|
44
45
|
size = screen.size()
|
|
45
46
|
screen_x = size.width()
|
|
46
47
|
screen_y = size.height()
|
|
48
|
+
|
|
49
|
+
# if sandbox, get resolution from plugin settings (Playwright viewport)
|
|
50
|
+
if is_sandbox:
|
|
51
|
+
try:
|
|
52
|
+
screen_x = int(self.window.core.plugins.get_option("cmd_mouse_control", "sandbox_viewport_w"))
|
|
53
|
+
screen_y = int(self.window.core.plugins.get_option("cmd_mouse_control", "sandbox_viewport_h"))
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
47
56
|
return {
|
|
48
57
|
"type": "computer_use_preview",
|
|
49
58
|
"display_width": screen_x,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.12.
|
|
9
|
+
# Updated Date: 2025.12.31 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import base64
|
|
@@ -59,6 +59,7 @@ class Image:
|
|
|
59
59
|
inline = extra.get("inline", False)
|
|
60
60
|
sub_mode = self.MODE_GENERATE
|
|
61
61
|
image_id = extra.get("image_id") # previous image reference for remix
|
|
62
|
+
extra_prompt = extra.get("extra_prompt", "")
|
|
62
63
|
|
|
63
64
|
# if attachments then switch mode to EDIT
|
|
64
65
|
attachments = context.attachments
|
|
@@ -74,28 +75,29 @@ class Image:
|
|
|
74
75
|
prompt_model = self.window.core.models.get(tmp_model)
|
|
75
76
|
|
|
76
77
|
# worker
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
worker = ImageWorker()
|
|
79
|
+
worker.window = self.window
|
|
80
|
+
worker.client = self.window.core.api.openai.get_client()
|
|
81
|
+
worker.ctx = ctx
|
|
82
|
+
worker.mode = sub_mode # mode can be "generate" or "edit"
|
|
83
|
+
worker.attachments = attachments # attachments for edit mode
|
|
84
|
+
worker.raw = self.window.core.config.get('img_raw')
|
|
85
|
+
worker.model = model.id # model ID for generate image, e.g. "dall-e-3"
|
|
86
|
+
worker.model_prompt = prompt_model # model for generate prompt, not image!
|
|
87
|
+
worker.input_prompt = prompt
|
|
88
|
+
worker.system_prompt = self.window.core.prompt.get('img')
|
|
89
|
+
worker.num = num
|
|
90
|
+
worker.inline = inline
|
|
91
|
+
worker.extra_prompt = extra_prompt
|
|
92
|
+
worker.image_id = image_id # remix: previous image path/identifier
|
|
91
93
|
|
|
92
94
|
# config
|
|
93
95
|
if self.window.core.config.has('img_quality'):
|
|
94
|
-
|
|
96
|
+
worker.quality = self.window.core.config.get('img_quality')
|
|
95
97
|
if self.window.core.config.has('img_resolution'):
|
|
96
|
-
|
|
98
|
+
worker.resolution = self.window.core.config.get('img_resolution')
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
self.worker = worker
|
|
99
101
|
self.worker.signals.finished.connect(self.window.core.image.handle_finished)
|
|
100
102
|
self.worker.signals.finished_inline.connect(self.window.core.image.handle_finished_inline)
|
|
101
103
|
self.worker.signals.status.connect(self.window.core.image.handle_status)
|
|
@@ -145,6 +147,7 @@ class ImageWorker(QRunnable):
|
|
|
145
147
|
self.input_prompt: Optional[str] = None
|
|
146
148
|
self.system_prompt = None
|
|
147
149
|
self.inline = False
|
|
150
|
+
self.extra_prompt: Optional[str] = None
|
|
148
151
|
self.num = 1
|
|
149
152
|
self.image_id: Optional[str] = None # previous image reference for remix
|
|
150
153
|
|
|
@@ -245,6 +248,13 @@ class ImageWorker(QRunnable):
|
|
|
245
248
|
self.signals.error.emit(e)
|
|
246
249
|
self.signals.status.emit(trans('img.status.prompt.error') + ": " + str(e))
|
|
247
250
|
|
|
251
|
+
# Fallback negative prompt injection (OpenAI Images API has no native negative_prompt field)
|
|
252
|
+
if self.extra_prompt and str(self.extra_prompt).strip():
|
|
253
|
+
try:
|
|
254
|
+
self.input_prompt = self._merge_negative_prompt(self.input_prompt or "", self.extra_prompt)
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
|
|
248
258
|
self.signals.status.emit(trans('img.status.generating') + ": {}...".format(self.input_prompt))
|
|
249
259
|
|
|
250
260
|
paths: List[str] = [] # downloaded images paths
|
|
@@ -407,4 +417,17 @@ class ImageWorker(QRunnable):
|
|
|
407
417
|
try:
|
|
408
418
|
sig.deleteLater()
|
|
409
419
|
except RuntimeError:
|
|
410
|
-
pass
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
# ---------- prompt utilities ----------
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def _merge_negative_prompt(prompt: str, negative: Optional[str]) -> str:
|
|
426
|
+
"""
|
|
427
|
+
Append a negative prompt to the main text prompt for providers without a native negative_prompt field.
|
|
428
|
+
"""
|
|
429
|
+
base = (prompt or "").strip()
|
|
430
|
+
neg = (negative or "").strip()
|
|
431
|
+
if not neg:
|
|
432
|
+
return base
|
|
433
|
+
return (base + ("\n" if base else "") + f"Negative prompt: {neg}").strip()
|
|
@@ -6,13 +6,13 @@
|
|
|
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 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
13
|
from typing import Optional, List
|
|
14
14
|
|
|
15
|
-
from pygpt_net.item.
|
|
15
|
+
from pygpt_net.item.store import RemoteStoreItem
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class Store:
|
|
@@ -273,7 +273,7 @@ class Store:
|
|
|
273
273
|
for remote in stores.data:
|
|
274
274
|
id = remote.id
|
|
275
275
|
if id not in items:
|
|
276
|
-
items[id] =
|
|
276
|
+
items[id] = RemoteStoreItem()
|
|
277
277
|
tmp_name = remote.name
|
|
278
278
|
if tmp_name is None:
|
|
279
279
|
items[id].is_thread = True # tmp store for thread
|
|
@@ -281,8 +281,8 @@ class Store:
|
|
|
281
281
|
items[id].id = id
|
|
282
282
|
items[id].name = tmp_name
|
|
283
283
|
items[id].file_ids = []
|
|
284
|
-
items[id].status = self.window.core.
|
|
285
|
-
self.window.core.
|
|
284
|
+
items[id].status = self.window.core.remote_store.openai.parse_status(remote)
|
|
285
|
+
self.window.core.remote_store.openai.append_status(items[id], items[id].status)
|
|
286
286
|
self.log("Imported vector store: " + id, callback)
|
|
287
287
|
# next page
|
|
288
288
|
if stores.has_more:
|
|
@@ -633,7 +633,7 @@ class Store:
|
|
|
633
633
|
if id not in items:
|
|
634
634
|
items.append(id)
|
|
635
635
|
data = self.get_file(remote.id)
|
|
636
|
-
self.window.core.
|
|
636
|
+
self.window.core.remote_store.openai.files.insert(store_id, data) # add remote file to DB
|
|
637
637
|
msg = "Imported file ID {} to store {}".format(remote.id, store_id)
|
|
638
638
|
self.log(msg, callback)
|
|
639
639
|
except Exception as e:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.12.
|
|
9
|
+
# Updated Date: 2025.12.31 16:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
@@ -60,6 +60,7 @@ class Video:
|
|
|
60
60
|
num = int(extra.get("num", 1))
|
|
61
61
|
inline = bool(extra.get("inline", False))
|
|
62
62
|
video_id = extra.get("video_id")
|
|
63
|
+
extra_prompt = extra.get("extra_prompt", "")
|
|
63
64
|
|
|
64
65
|
# decide sub-mode based on attachments (image-to-video when image is attached)
|
|
65
66
|
sub_mode = self.MODE_GENERATE
|
|
@@ -86,6 +87,7 @@ class Video:
|
|
|
86
87
|
worker.raw = self.window.core.config.get('img_raw')
|
|
87
88
|
worker.num = num
|
|
88
89
|
worker.inline = inline
|
|
90
|
+
worker.extra_prompt = extra_prompt
|
|
89
91
|
worker.video_id = video_id
|
|
90
92
|
|
|
91
93
|
# optional params (app-level options)
|
|
@@ -157,6 +159,7 @@ class VideoWorker(QRunnable):
|
|
|
157
159
|
self.input_prompt = ""
|
|
158
160
|
self.system_prompt = ""
|
|
159
161
|
self.inline = False
|
|
162
|
+
self.extra_prompt: Optional[str] = None
|
|
160
163
|
self.video_id = None
|
|
161
164
|
self.raw = False
|
|
162
165
|
self.num = 1
|
|
@@ -193,6 +196,14 @@ class VideoWorker(QRunnable):
|
|
|
193
196
|
self.signals.error.emit(e)
|
|
194
197
|
self.signals.status.emit(trans('vid.status.prompt.error') + ": " + str(e))
|
|
195
198
|
|
|
199
|
+
# Negative prompt fallback: inject constraints into the text prompt (Sora has no native negative_prompt field)
|
|
200
|
+
if self.extra_prompt and str(self.extra_prompt).strip():
|
|
201
|
+
try:
|
|
202
|
+
self.input_prompt = self._merge_negative_prompt(self.input_prompt, self.extra_prompt)
|
|
203
|
+
except Exception:
|
|
204
|
+
# do not fail generation if merge fails
|
|
205
|
+
pass
|
|
206
|
+
|
|
196
207
|
# Sora API accepts a single video per create call; honor app's num but cap to 1 per job
|
|
197
208
|
_ = max(1, min(self.num, self.max_per_job))
|
|
198
209
|
|
|
@@ -609,4 +620,18 @@ class VideoWorker(QRunnable):
|
|
|
609
620
|
except Exception:
|
|
610
621
|
pass
|
|
611
622
|
|
|
612
|
-
return " ".join(parts).strip()
|
|
623
|
+
return " ".join(parts).strip()
|
|
624
|
+
|
|
625
|
+
# ---------- prompt utilities ----------
|
|
626
|
+
|
|
627
|
+
@staticmethod
|
|
628
|
+
def _merge_negative_prompt(prompt: str, negative: Optional[str]) -> str:
|
|
629
|
+
"""
|
|
630
|
+
Append a negative prompt to the main text prompt for providers without a native negative_prompt field.
|
|
631
|
+
"""
|
|
632
|
+
base = (prompt or "").strip()
|
|
633
|
+
neg = (negative or "").strip()
|
|
634
|
+
if not neg:
|
|
635
|
+
return base
|
|
636
|
+
# Keep the user's original prompt intact and add clear constraint instructions.
|
|
637
|
+
return (base + ("\n" if base else "") + f"Negative prompt: {neg}").strip()
|