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,190 @@
|
|
|
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 Szczyglinski #
|
|
9
|
+
# Updated Date: 2026.01.02 02:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import time
|
|
14
|
+
from typing import Dict, Any, List, Tuple, Optional
|
|
15
|
+
|
|
16
|
+
from google.genai import types as gtypes
|
|
17
|
+
from pygpt_net.item.ctx import CtxItem
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Computer:
|
|
21
|
+
def __init__(self, window=None):
|
|
22
|
+
"""
|
|
23
|
+
Google Gemini Computer Use adapter
|
|
24
|
+
|
|
25
|
+
This adapter passes Google Gemini function calls and action events
|
|
26
|
+
directly to the plugin worker without translating names to legacy
|
|
27
|
+
commands. The workers (host and sandbox) implement the full set
|
|
28
|
+
of functions and handle any coordinate conversions themselves.
|
|
29
|
+
"""
|
|
30
|
+
self.window = window
|
|
31
|
+
self._seq = 0
|
|
32
|
+
|
|
33
|
+
# --------------- Tool spec --------------- #
|
|
34
|
+
|
|
35
|
+
def get_current_env(self) -> Dict[str, Any]:
|
|
36
|
+
idx = self.window.ui.nodes["computer_env"].currentIndex()
|
|
37
|
+
return self.window.ui.nodes["computer_env"].itemData(idx)
|
|
38
|
+
|
|
39
|
+
def _map_env(self) -> gtypes.Environment:
|
|
40
|
+
return gtypes.Environment.ENVIRONMENT_BROWSER
|
|
41
|
+
env = self.get_current_env()
|
|
42
|
+
val = ""
|
|
43
|
+
if isinstance(env, str):
|
|
44
|
+
val = env.lower()
|
|
45
|
+
elif isinstance(env, dict):
|
|
46
|
+
val = str(env.get("value") or env.get("name") or env).lower()
|
|
47
|
+
if "mac" in val:
|
|
48
|
+
return gtypes.Environment.ENVIRONMENT_MAC
|
|
49
|
+
if "windows" in val or "win" in val:
|
|
50
|
+
return gtypes.Environment.ENVIRONMENT_WINDOWS
|
|
51
|
+
if "linux" in val:
|
|
52
|
+
return gtypes.Environment.ENVIRONMENT_LINUX
|
|
53
|
+
return gtypes.Environment.ENVIRONMENT_BROWSER
|
|
54
|
+
|
|
55
|
+
def get_tool(self) -> gtypes.Tool:
|
|
56
|
+
return gtypes.Tool(
|
|
57
|
+
computer_use=gtypes.ComputerUse(
|
|
58
|
+
environment=self._map_env(),
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# --------------- Streaming handling --------------- #
|
|
63
|
+
|
|
64
|
+
def _next_id(self) -> str:
|
|
65
|
+
self._seq += 1
|
|
66
|
+
return f"gc-{int(time.time()*1000)}-{self._seq}"
|
|
67
|
+
|
|
68
|
+
def _append_call(self, tool_calls: list, id_: str, call_id: str, name: str, args: dict) -> None:
|
|
69
|
+
tool_calls.append({
|
|
70
|
+
"id": id_,
|
|
71
|
+
"call_id": call_id,
|
|
72
|
+
"type": "computer_call",
|
|
73
|
+
"function": {
|
|
74
|
+
"name": name,
|
|
75
|
+
"arguments": json.dumps(args or {}),
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
def _append_screenshot(self, tool_calls: list, id_: str, call_id: str) -> None:
|
|
80
|
+
self._append_call(tool_calls, id_, call_id, "get_screenshot", {})
|
|
81
|
+
|
|
82
|
+
def _record_pending_checks(self, ctx: CtxItem, pending: Optional[list]) -> None:
|
|
83
|
+
if not pending:
|
|
84
|
+
return
|
|
85
|
+
ctx.extra["pending_safety_checks"] = []
|
|
86
|
+
for item in pending:
|
|
87
|
+
try:
|
|
88
|
+
ctx.extra["pending_safety_checks"].append({
|
|
89
|
+
"id": getattr(item, "id", None),
|
|
90
|
+
"code": getattr(item, "code", None),
|
|
91
|
+
"message": getattr(item, "message", None),
|
|
92
|
+
})
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def handle_stream_chunk(self, ctx: CtxItem, chunk, tool_calls: list) -> Tuple[List, bool]:
|
|
97
|
+
"""
|
|
98
|
+
Handle function_call parts (Gemini) and older action-shaped events.
|
|
99
|
+
All functions are passed through unchanged to the worker.
|
|
100
|
+
|
|
101
|
+
Returns: updated tool_calls and a boolean indicating if there were calls.
|
|
102
|
+
"""
|
|
103
|
+
has_calls = False
|
|
104
|
+
|
|
105
|
+
# Case A: Google SDK function_call parts (recommended)
|
|
106
|
+
for fname, fargs in self._iter_function_calls(chunk):
|
|
107
|
+
if not fname:
|
|
108
|
+
continue
|
|
109
|
+
id_ = self._next_id()
|
|
110
|
+
call_id = id_
|
|
111
|
+
try:
|
|
112
|
+
self._append_call(tool_calls, id_, call_id, fname, fargs or {})
|
|
113
|
+
has_calls = True
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"Gemini pass-through error for function '{fname}': {e}")
|
|
116
|
+
|
|
117
|
+
# Case B: Older/OpenAI-shaped events embedded as chunk.item.action
|
|
118
|
+
try:
|
|
119
|
+
item = getattr(chunk, "item", None)
|
|
120
|
+
if item and getattr(item, "type", "") == "computer_call":
|
|
121
|
+
id_ = getattr(item, "id", None) or self._next_id()
|
|
122
|
+
call_id = getattr(item, "call_id", None) or id_
|
|
123
|
+
action = getattr(item, "action", None)
|
|
124
|
+
if action:
|
|
125
|
+
name, args = self._pass_action(action)
|
|
126
|
+
if name:
|
|
127
|
+
self._append_call(tool_calls, id_, call_id, name, args)
|
|
128
|
+
has_calls = True
|
|
129
|
+
# optional pending safety checks
|
|
130
|
+
if getattr(item, "pending_safety_checks", None):
|
|
131
|
+
self._record_pending_checks(ctx, item.pending_safety_checks)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f"Gemini action stream parse error: {e}")
|
|
134
|
+
|
|
135
|
+
return tool_calls, has_calls
|
|
136
|
+
|
|
137
|
+
# --------------- Parsers --------------- #
|
|
138
|
+
|
|
139
|
+
def _iter_function_calls(self, resp) -> List[tuple]:
|
|
140
|
+
calls = []
|
|
141
|
+
try:
|
|
142
|
+
candidates = getattr(resp, "candidates", None)
|
|
143
|
+
if candidates:
|
|
144
|
+
for cand in candidates:
|
|
145
|
+
content = getattr(cand, "content", None)
|
|
146
|
+
if content:
|
|
147
|
+
parts = getattr(content, "parts", None)
|
|
148
|
+
if parts:
|
|
149
|
+
for part in parts:
|
|
150
|
+
fc = getattr(part, "function_call", None)
|
|
151
|
+
if fc:
|
|
152
|
+
name = getattr(fc, "name", None)
|
|
153
|
+
args = getattr(fc, "args", {}) or {}
|
|
154
|
+
calls.append((name, args))
|
|
155
|
+
else:
|
|
156
|
+
if isinstance(resp, dict):
|
|
157
|
+
content = resp.get("content", {})
|
|
158
|
+
parts = content.get("parts", [])
|
|
159
|
+
for part in parts:
|
|
160
|
+
if "function_call" in part:
|
|
161
|
+
fc = part["function_call"]
|
|
162
|
+
calls.append((fc.get("name"), fc.get("args", {})))
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f"Gemini: failed to parse function_call: {e}")
|
|
165
|
+
return calls
|
|
166
|
+
|
|
167
|
+
def _pass_action(self, action) -> Tuple[Optional[str], dict]:
|
|
168
|
+
"""
|
|
169
|
+
Convert old-style action object into a direct function call name + args,
|
|
170
|
+
without changing the semantic names (workers handle details).
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
atype = getattr(action, "type", None)
|
|
174
|
+
if not atype:
|
|
175
|
+
return None, {}
|
|
176
|
+
atype = str(atype)
|
|
177
|
+
|
|
178
|
+
# Build args by introspection; workers know how to interpret them
|
|
179
|
+
args = {}
|
|
180
|
+
for attr in ("x", "y", "button", "scroll_x", "scroll_y", "keys", "text", "path"):
|
|
181
|
+
if hasattr(action, attr):
|
|
182
|
+
args[attr] = getattr(action, attr)
|
|
183
|
+
|
|
184
|
+
if atype == "double_click":
|
|
185
|
+
args["num_clicks"] = 2
|
|
186
|
+
|
|
187
|
+
return atype, args
|
|
188
|
+
except Exception as e:
|
|
189
|
+
print(f"Gemini: pass_action error: {e}")
|
|
190
|
+
return None, {}
|
|
@@ -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 mimetypes
|
|
@@ -55,6 +55,7 @@ class Image:
|
|
|
55
55
|
prompt = context.prompt
|
|
56
56
|
num = int(extra.get("num", 1))
|
|
57
57
|
inline = bool(extra.get("inline", False))
|
|
58
|
+
extra_prompt = extra.get("extra_prompt", "")
|
|
58
59
|
|
|
59
60
|
# decide sub-mode based on attachments
|
|
60
61
|
sub_mode = self.MODE_GENERATE
|
|
@@ -79,6 +80,7 @@ class Image:
|
|
|
79
80
|
worker.raw = self.window.core.config.get('img_raw')
|
|
80
81
|
worker.num = num
|
|
81
82
|
worker.inline = inline
|
|
83
|
+
worker.extra_prompt = extra_prompt
|
|
82
84
|
|
|
83
85
|
# remix: previous image reference (ID/URI/path) from extra
|
|
84
86
|
worker.image_id = extra.get("image_id")
|
|
@@ -129,6 +131,7 @@ class ImageWorker(QRunnable):
|
|
|
129
131
|
self.input_prompt = ""
|
|
130
132
|
self.system_prompt = ""
|
|
131
133
|
self.inline = False
|
|
134
|
+
self.extra_prompt: Optional[str] = None
|
|
132
135
|
self.raw = False
|
|
133
136
|
self.num = 1
|
|
134
137
|
self.resolution = "1024x1024" # used to derive aspect ratio or image_size
|
|
@@ -178,6 +181,18 @@ class ImageWorker(QRunnable):
|
|
|
178
181
|
self.signals.error.emit(e)
|
|
179
182
|
self.signals.status.emit(trans('img.status.prompt.error') + ": " + str(e))
|
|
180
183
|
|
|
184
|
+
# Decide how to apply negative prompt: native param on Vertex Imagen 3.0 (-001) or inline fallback.
|
|
185
|
+
use_param = (
|
|
186
|
+
bool(self.extra_prompt and str(self.extra_prompt).strip())
|
|
187
|
+
and self._using_vertex()
|
|
188
|
+
and self._imagen_supports_negative_prompt(self.model)
|
|
189
|
+
)
|
|
190
|
+
if (self.extra_prompt and str(self.extra_prompt).strip()) and not use_param:
|
|
191
|
+
try:
|
|
192
|
+
self.input_prompt = self._merge_negative_prompt(self.input_prompt or "", self.extra_prompt)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
|
|
181
196
|
paths: List[str] = []
|
|
182
197
|
|
|
183
198
|
# Remix path: if image_id provided, prefer image-to-image remix using the given identifier.
|
|
@@ -198,11 +213,21 @@ class ImageWorker(QRunnable):
|
|
|
198
213
|
mask_dilation=0.0,
|
|
199
214
|
),
|
|
200
215
|
)
|
|
201
|
-
|
|
216
|
+
# Prepare edit config with optional negative prompt when supported
|
|
217
|
+
cfg_kwargs = dict(
|
|
202
218
|
edit_mode="EDIT_MODE_DEFAULT",
|
|
203
219
|
number_of_images=min(self.num, self.imagen_max_num),
|
|
204
220
|
include_rai_reason=True,
|
|
205
221
|
)
|
|
222
|
+
if self.extra_prompt and self._imagen_supports_negative_prompt(self.model):
|
|
223
|
+
cfg_kwargs["negative_prompt"] = self.extra_prompt
|
|
224
|
+
try:
|
|
225
|
+
cfg = gtypes.EditImageConfig(**cfg_kwargs)
|
|
226
|
+
except Exception:
|
|
227
|
+
# Fallback without negative_prompt if SDK doesn't recognize it
|
|
228
|
+
cfg_kwargs.pop("negative_prompt", None)
|
|
229
|
+
cfg = gtypes.EditImageConfig(**cfg_kwargs)
|
|
230
|
+
|
|
206
231
|
resp = self.client.models.edit_image(
|
|
207
232
|
model="imagen-3.0-capability-001",
|
|
208
233
|
prompt=self.input_prompt or "",
|
|
@@ -355,12 +380,34 @@ class ImageWorker(QRunnable):
|
|
|
355
380
|
mid = str(model_id).lower()
|
|
356
381
|
return "imagen" in mid and "generate" in mid
|
|
357
382
|
|
|
383
|
+
def _imagen_supports_negative_prompt(self, model_id: str) -> bool:
|
|
384
|
+
"""
|
|
385
|
+
Return True if the Imagen model supports native negative_prompt.
|
|
386
|
+
Supported: imagen-3.0-generate-001, imagen-3.0-fast-generate-001, imagen-3.0-capability-001.
|
|
387
|
+
"""
|
|
388
|
+
mid = str(model_id or "").lower()
|
|
389
|
+
return any(x in mid for x in (
|
|
390
|
+
"imagen-3.0-generate-001",
|
|
391
|
+
"imagen-3.0-fast-generate-001",
|
|
392
|
+
"imagen-3.0-capability-001",
|
|
393
|
+
))
|
|
394
|
+
|
|
358
395
|
def _imagen_generate(self, prompt: str, num: int, resolution: str):
|
|
359
396
|
"""Imagen text-to-image."""
|
|
360
397
|
aspect = self._aspect_from_resolution(resolution)
|
|
361
|
-
|
|
398
|
+
# Build config with optional negative_prompt when supported by model and provided.
|
|
399
|
+
cfg_kwargs: Dict[str, Any] = {"number_of_images": num}
|
|
362
400
|
if aspect:
|
|
363
|
-
|
|
401
|
+
cfg_kwargs["aspect_ratio"] = aspect
|
|
402
|
+
if self.extra_prompt and self._imagen_supports_negative_prompt(self.model):
|
|
403
|
+
cfg_kwargs["negative_prompt"] = self.extra_prompt
|
|
404
|
+
try:
|
|
405
|
+
cfg = gtypes.GenerateImagesConfig(**cfg_kwargs)
|
|
406
|
+
except Exception:
|
|
407
|
+
# Fallback without negative_prompt if SDK doesn't recognize it
|
|
408
|
+
cfg_kwargs.pop("negative_prompt", None)
|
|
409
|
+
cfg = gtypes.GenerateImagesConfig(**cfg_kwargs)
|
|
410
|
+
|
|
364
411
|
return self.client.models.generate_images(
|
|
365
412
|
model=self.model,
|
|
366
413
|
prompt=prompt,
|
|
@@ -401,11 +448,19 @@ class ImageWorker(QRunnable):
|
|
|
401
448
|
)
|
|
402
449
|
edit_mode = "EDIT_MODE_BGSWAP"
|
|
403
450
|
|
|
404
|
-
|
|
451
|
+
# Build edit config with optional negative_prompt
|
|
452
|
+
cfg_kwargs = dict(
|
|
405
453
|
edit_mode=edit_mode,
|
|
406
454
|
number_of_images=min(num, self.imagen_max_num),
|
|
407
455
|
include_rai_reason=True,
|
|
408
456
|
)
|
|
457
|
+
if self.extra_prompt and self._imagen_supports_negative_prompt(self.model):
|
|
458
|
+
cfg_kwargs["negative_prompt"] = self.extra_prompt
|
|
459
|
+
try:
|
|
460
|
+
cfg = gtypes.EditImageConfig(**cfg_kwargs)
|
|
461
|
+
except Exception:
|
|
462
|
+
cfg_kwargs.pop("negative_prompt", None)
|
|
463
|
+
cfg = gtypes.EditImageConfig(**cfg_kwargs)
|
|
409
464
|
|
|
410
465
|
# Ensure capability model for edit
|
|
411
466
|
model_id = "imagen-3.0-capability-001"
|
|
@@ -806,4 +861,17 @@ class ImageWorker(QRunnable):
|
|
|
806
861
|
try:
|
|
807
862
|
sig.deleteLater()
|
|
808
863
|
except RuntimeError:
|
|
809
|
-
pass
|
|
864
|
+
pass
|
|
865
|
+
|
|
866
|
+
# ---------- prompt utilities ----------
|
|
867
|
+
|
|
868
|
+
@staticmethod
|
|
869
|
+
def _merge_negative_prompt(prompt: str, negative: Optional[str]) -> str:
|
|
870
|
+
"""
|
|
871
|
+
Append a negative prompt to the main text prompt when the provider has no native negative_prompt field.
|
|
872
|
+
"""
|
|
873
|
+
base = (prompt or "").strip()
|
|
874
|
+
neg = (negative or "").strip()
|
|
875
|
+
if not neg:
|
|
876
|
+
return base
|
|
877
|
+
return (base + ("\n" if base else "") + f"Negative prompt: {neg}").strip()
|
|
@@ -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 19:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -97,7 +97,7 @@ class Realtime:
|
|
|
97
97
|
|
|
98
98
|
# Tools
|
|
99
99
|
tools = self.window.core.api.google.tools.prepare(model, context.external_functions)
|
|
100
|
-
remote_tools = self.window.core.api.google.build_remote_tools(model)
|
|
100
|
+
remote_tools = self.window.core.api.google.remote_tools.build_remote_tools(model)
|
|
101
101
|
if tools:
|
|
102
102
|
remote_tools = [] # in Google, remote tools are not allowed if function calling is used
|
|
103
103
|
|
|
@@ -0,0 +1,93 @@
|
|
|
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 19:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from google.genai import types as gtypes
|
|
13
|
+
|
|
14
|
+
from pygpt_net.item.model import ModelItem
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RemoteTools:
|
|
18
|
+
def __init__(self, window=None):
|
|
19
|
+
"""
|
|
20
|
+
Remote Tools helpers for Google GenAI.
|
|
21
|
+
|
|
22
|
+
:param window: Window instance
|
|
23
|
+
"""
|
|
24
|
+
self.window = window
|
|
25
|
+
|
|
26
|
+
def build_remote_tools(self, model: ModelItem = None) -> list:
|
|
27
|
+
"""
|
|
28
|
+
Build Google GenAI remote tools based on config flags.
|
|
29
|
+
- remote_tools.google.web_search: enables grounding via Google Search (Gemini 2.x)
|
|
30
|
+
or GoogleSearchRetrieval (Gemini 1.5 fallback).
|
|
31
|
+
- remote_tools.google.code_interpreter: enables code execution tool.
|
|
32
|
+
|
|
33
|
+
Returns a list of gtypes.Tool objects (can be empty).
|
|
34
|
+
|
|
35
|
+
:param model: ModelItem
|
|
36
|
+
:return: list of gtypes.Tool
|
|
37
|
+
"""
|
|
38
|
+
tools: list = []
|
|
39
|
+
cfg = self.window.core.config
|
|
40
|
+
model_id = (model.id if model and getattr(model, "id", None) else "").lower()
|
|
41
|
+
is_web = self.window.controller.chat.remote_tools.enabled(model, "web_search") # get global config
|
|
42
|
+
|
|
43
|
+
# Google Search tool
|
|
44
|
+
if is_web and "image" not in model.id:
|
|
45
|
+
try:
|
|
46
|
+
if not model_id.startswith("gemini-1.5") and not model_id.startswith("models/gemini-1.5"):
|
|
47
|
+
# Gemini 2.x uses GoogleSearch
|
|
48
|
+
tools.append(gtypes.Tool(google_search=gtypes.GoogleSearch()))
|
|
49
|
+
else:
|
|
50
|
+
# Gemini 1.5 fallback uses GoogleSearchRetrieval
|
|
51
|
+
# Note: Supported only for 1.5 models.
|
|
52
|
+
tools.append(gtypes.Tool(
|
|
53
|
+
google_search_retrieval=gtypes.GoogleSearchRetrieval()
|
|
54
|
+
))
|
|
55
|
+
except Exception as e:
|
|
56
|
+
# Do not break the request if tool construction fails
|
|
57
|
+
self.window.core.debug.log(e)
|
|
58
|
+
|
|
59
|
+
# Code Execution tool
|
|
60
|
+
if cfg.get("remote_tools.google.code_interpreter") and "image" not in model.id:
|
|
61
|
+
try:
|
|
62
|
+
tools.append(gtypes.Tool(code_execution=gtypes.ToolCodeExecution))
|
|
63
|
+
except Exception as e:
|
|
64
|
+
self.window.core.debug.log(e)
|
|
65
|
+
|
|
66
|
+
# URL Context tool
|
|
67
|
+
if cfg.get("remote_tools.google.url_ctx") and "image" not in model.id:
|
|
68
|
+
try:
|
|
69
|
+
# Supported on Gemini 2.x+ models (not on 1.5)
|
|
70
|
+
if not model_id.startswith("gemini-1.5") and not model_id.startswith("models/gemini-1.5"):
|
|
71
|
+
tools.append(gtypes.Tool(url_context=gtypes.UrlContext))
|
|
72
|
+
except Exception as e:
|
|
73
|
+
self.window.core.debug.log(e)
|
|
74
|
+
|
|
75
|
+
# Google Maps
|
|
76
|
+
if cfg.get("remote_tools.google.maps") and "image" not in model.id:
|
|
77
|
+
try:
|
|
78
|
+
tools.append(gtypes.Tool(google_maps=gtypes.GoogleMaps()))
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.window.core.debug.log(e)
|
|
81
|
+
|
|
82
|
+
# File search
|
|
83
|
+
if cfg.get("remote_tools.google.file_search") and "image" not in model.id:
|
|
84
|
+
store_ids = cfg.get("remote_tools.google.file_search.args", "")
|
|
85
|
+
file_search_store_names = [s.strip() for s in store_ids.split(",") if s.strip()]
|
|
86
|
+
try:
|
|
87
|
+
tools.append(gtypes.Tool(file_search=gtypes.FileSearch(
|
|
88
|
+
file_search_store_names=file_search_store_names,
|
|
89
|
+
)))
|
|
90
|
+
except Exception as e:
|
|
91
|
+
self.window.core.debug.log(e)
|
|
92
|
+
|
|
93
|
+
return tools
|