pygpt-net 2.6.58__py3-none-any.whl → 2.6.60__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 +10 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +9 -5
- pygpt_net/controller/__init__.py +1 -0
- pygpt_net/controller/presets/editor.py +442 -39
- pygpt_net/core/agents/custom/__init__.py +275 -0
- pygpt_net/core/agents/custom/debug.py +64 -0
- pygpt_net/core/agents/custom/factory.py +109 -0
- pygpt_net/core/agents/custom/graph.py +71 -0
- pygpt_net/core/agents/custom/llama_index/__init__.py +10 -0
- pygpt_net/core/agents/custom/llama_index/factory.py +89 -0
- pygpt_net/core/agents/custom/llama_index/router_streamer.py +106 -0
- pygpt_net/core/agents/custom/llama_index/runner.py +529 -0
- pygpt_net/core/agents/custom/llama_index/stream.py +56 -0
- pygpt_net/core/agents/custom/llama_index/utils.py +242 -0
- pygpt_net/core/agents/custom/logging.py +50 -0
- pygpt_net/core/agents/custom/memory.py +51 -0
- pygpt_net/core/agents/custom/router.py +116 -0
- pygpt_net/core/agents/custom/router_streamer.py +187 -0
- pygpt_net/core/agents/custom/runner.py +454 -0
- pygpt_net/core/agents/custom/schema.py +125 -0
- pygpt_net/core/agents/custom/utils.py +181 -0
- pygpt_net/core/agents/provider.py +72 -7
- pygpt_net/core/agents/runner.py +7 -4
- pygpt_net/core/agents/runners/helpers.py +1 -1
- pygpt_net/core/agents/runners/llama_workflow.py +3 -0
- pygpt_net/core/agents/runners/openai_workflow.py +8 -1
- pygpt_net/core/filesystem/parser.py +37 -24
- pygpt_net/{ui/widget/builder → core/node_editor}/__init__.py +2 -2
- pygpt_net/core/{builder → node_editor}/graph.py +11 -218
- pygpt_net/core/node_editor/models.py +111 -0
- pygpt_net/core/node_editor/types.py +76 -0
- pygpt_net/core/node_editor/utils.py +17 -0
- pygpt_net/core/render/web/renderer.py +10 -8
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/locale/locale.en.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_system.en.ini +68 -0
- pygpt_net/item/agent.py +5 -1
- pygpt_net/item/preset.py +19 -1
- pygpt_net/plugin/cmd_system/config.py +377 -1
- pygpt_net/plugin/cmd_system/plugin.py +52 -8
- pygpt_net/plugin/cmd_system/runner.py +508 -32
- pygpt_net/plugin/cmd_system/winapi.py +481 -0
- pygpt_net/plugin/cmd_system/worker.py +88 -15
- pygpt_net/provider/agents/base.py +33 -2
- pygpt_net/provider/agents/llama_index/flow_from_schema.py +92 -0
- pygpt_net/provider/agents/llama_index/workflow/supervisor.py +0 -0
- pygpt_net/provider/agents/openai/flow_from_schema.py +96 -0
- pygpt_net/provider/core/agent/json_file.py +11 -5
- pygpt_net/provider/llms/openai.py +6 -4
- pygpt_net/tools/agent_builder/tool.py +217 -52
- pygpt_net/tools/agent_builder/ui/dialogs.py +119 -24
- pygpt_net/tools/agent_builder/ui/list.py +37 -10
- pygpt_net/tools/code_interpreter/ui/html.py +2 -1
- pygpt_net/ui/dialog/preset.py +16 -1
- pygpt_net/ui/main.py +1 -1
- pygpt_net/{core/builder → ui/widget/node_editor}/__init__.py +2 -2
- pygpt_net/ui/widget/node_editor/command.py +373 -0
- pygpt_net/ui/widget/node_editor/editor.py +2038 -0
- pygpt_net/ui/widget/node_editor/item.py +492 -0
- pygpt_net/ui/widget/node_editor/node.py +1205 -0
- pygpt_net/ui/widget/node_editor/utils.py +17 -0
- pygpt_net/ui/widget/node_editor/view.py +247 -0
- pygpt_net/ui/widget/textarea/web.py +1 -1
- {pygpt_net-2.6.58.dist-info → pygpt_net-2.6.60.dist-info}/METADATA +135 -61
- {pygpt_net-2.6.58.dist-info → pygpt_net-2.6.60.dist-info}/RECORD +69 -42
- pygpt_net/core/agents/custom.py +0 -150
- pygpt_net/ui/widget/builder/editor.py +0 -2001
- {pygpt_net-2.6.58.dist-info → pygpt_net-2.6.60.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.58.dist-info → pygpt_net-2.6.60.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.58.dist-info → pygpt_net-2.6.60.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
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: 2025.09.23 07:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
import platform
|
|
13
|
+
import ctypes
|
|
14
|
+
from ctypes import wintypes
|
|
15
|
+
from typing import Dict, List, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
# Guard for non-Windows platforms: provide stubs that fail at call-time
|
|
18
|
+
if platform.system() != "Windows":
|
|
19
|
+
class WinAPI:
|
|
20
|
+
def __init__(self): raise RuntimeError("Windows only")
|
|
21
|
+
class InputSender:
|
|
22
|
+
def __init__(self): raise RuntimeError("Windows only")
|
|
23
|
+
else:
|
|
24
|
+
user32 = ctypes.windll.user32
|
|
25
|
+
kernel32 = ctypes.windll.kernel32
|
|
26
|
+
psapi = ctypes.windll.psapi
|
|
27
|
+
gdi32 = ctypes.windll.gdi32
|
|
28
|
+
|
|
29
|
+
# Constants
|
|
30
|
+
GWL_EXSTYLE = -20
|
|
31
|
+
WS_EX_LAYERED = 0x00080000
|
|
32
|
+
LWA_ALPHA = 0x00000002
|
|
33
|
+
|
|
34
|
+
SW_HIDE = 0
|
|
35
|
+
SW_SHOWNORMAL = 1
|
|
36
|
+
SW_SHOWMINIMIZED = 2
|
|
37
|
+
SW_SHOWMAXIMIZED = 3
|
|
38
|
+
SW_RESTORE = 9
|
|
39
|
+
|
|
40
|
+
SWP_NOSIZE = 0x0001
|
|
41
|
+
SWP_NOMOVE = 0x0002
|
|
42
|
+
SWP_NOZORDER = 0x0004
|
|
43
|
+
SWP_NOACTIVATE = 0x0010
|
|
44
|
+
HWND_TOP = 0
|
|
45
|
+
HWND_TOPMOST = -1
|
|
46
|
+
HWND_NOTOPMOST = -2
|
|
47
|
+
|
|
48
|
+
WM_CLOSE = 0x0010
|
|
49
|
+
SMTO_BLOCK = 0x0001
|
|
50
|
+
SMTO_ABORTIFHUNG = 0x0002
|
|
51
|
+
SEND_TIMEOUT = 2000
|
|
52
|
+
|
|
53
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
|
54
|
+
PROCESS_VM_READ = 0x0010
|
|
55
|
+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
56
|
+
|
|
57
|
+
# Input constants
|
|
58
|
+
INPUT_MOUSE = 0
|
|
59
|
+
INPUT_KEYBOARD = 1
|
|
60
|
+
|
|
61
|
+
KEYEVENTF_KEYUP = 0x0002
|
|
62
|
+
KEYEVENTF_UNICODE = 0x0004
|
|
63
|
+
|
|
64
|
+
MOUSEEVENTF_MOVE = 0x0001
|
|
65
|
+
MOUSEEVENTF_LEFTDOWN = 0x0002
|
|
66
|
+
MOUSEEVENTF_LEFTUP = 0x0004
|
|
67
|
+
MOUSEEVENTF_RIGHTDOWN = 0x0008
|
|
68
|
+
MOUSEEVENTF_RIGHTUP = 0x0010
|
|
69
|
+
MOUSEEVENTF_MIDDLEDOWN = 0x0020
|
|
70
|
+
MOUSEEVENTF_MIDDLEUP = 0x0040
|
|
71
|
+
|
|
72
|
+
# VK map
|
|
73
|
+
VK = {
|
|
74
|
+
"SHIFT": 0x10, "CTRL": 0x11, "ALT": 0x12, "WIN": 0x5B,
|
|
75
|
+
"ENTER": 0x0D, "ESC": 0x1B, "TAB": 0x09, "SPACE": 0x20, "BACKSPACE": 0x08, "DELETE": 0x2E,
|
|
76
|
+
"UP": 0x26, "DOWN": 0x28, "LEFT": 0x25, "RIGHT": 0x27,
|
|
77
|
+
"HOME": 0x24, "END": 0x23, "PGUP": 0x21, "PGDN": 0x22,
|
|
78
|
+
"F1": 0x70, "F2": 0x71, "F3": 0x72, "F4": 0x73, "F5": 0x74, "F6": 0x75,
|
|
79
|
+
"F7": 0x76, "F8": 0x77, "F9": 0x78, "F10": 0x79, "F11": 0x7A, "F12": 0x7B,
|
|
80
|
+
"F13": 0x7C, "F14": 0x7D, "F15": 0x7E, "F16": 0x7F, "F17": 0x80, "F18": 0x81,
|
|
81
|
+
"F19": 0x82, "F20": 0x83, "F21": 0x84, "F22": 0x85, "F23": 0x86, "F24": 0x87,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if hasattr(wintypes, "ULONG_PTR"):
|
|
85
|
+
ULONG_PTR = wintypes.ULONG_PTR
|
|
86
|
+
else:
|
|
87
|
+
ULONG_PTR = ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong
|
|
88
|
+
|
|
89
|
+
if ctypes.sizeof(ctypes.c_void_p) == 8:
|
|
90
|
+
LONG_PTR = ctypes.c_longlong
|
|
91
|
+
else:
|
|
92
|
+
LONG_PTR = ctypes.c_long
|
|
93
|
+
|
|
94
|
+
# Structures
|
|
95
|
+
class KEYBDINPUT(ctypes.Structure):
|
|
96
|
+
_fields_ = [
|
|
97
|
+
("wVk", wintypes.WORD),
|
|
98
|
+
("wScan", wintypes.WORD),
|
|
99
|
+
("dwFlags", wintypes.DWORD),
|
|
100
|
+
("time", wintypes.DWORD),
|
|
101
|
+
("dwExtraInfo", ULONG_PTR),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
class MOUSEINPUT(ctypes.Structure):
|
|
105
|
+
_fields_ = [
|
|
106
|
+
("dx", wintypes.LONG),
|
|
107
|
+
("dy", wintypes.LONG),
|
|
108
|
+
("mouseData", wintypes.DWORD),
|
|
109
|
+
("dwFlags", wintypes.DWORD),
|
|
110
|
+
("time", wintypes.DWORD),
|
|
111
|
+
("dwExtraInfo", ULONG_PTR),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
class HARDWAREINPUT(ctypes.Structure):
|
|
115
|
+
_fields_ = [
|
|
116
|
+
("uMsg", wintypes.DWORD),
|
|
117
|
+
("wParamL", wintypes.WORD),
|
|
118
|
+
("wParamH", wintypes.WORD),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
class INPUT_union(ctypes.Union):
|
|
122
|
+
_fields_ = [("ki", KEYBDINPUT), ("mi", MOUSEINPUT), ("hi", HARDWAREINPUT)]
|
|
123
|
+
|
|
124
|
+
class INPUT(ctypes.Structure):
|
|
125
|
+
_fields_ = [("type", wintypes.DWORD), ("union", INPUT_union)]
|
|
126
|
+
|
|
127
|
+
# Function prototypes (partial)
|
|
128
|
+
EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
|
129
|
+
EnumChildProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
|
130
|
+
|
|
131
|
+
user32.EnumWindows.restype = wintypes.BOOL
|
|
132
|
+
user32.EnumWindows.argtypes = (EnumWindowsProc, wintypes.LPARAM)
|
|
133
|
+
user32.EnumChildWindows.restype = wintypes.BOOL
|
|
134
|
+
user32.EnumChildWindows.argtypes = (wintypes.HWND, EnumChildProc, wintypes.LPARAM)
|
|
135
|
+
|
|
136
|
+
user32.SendInput.restype = wintypes.UINT
|
|
137
|
+
user32.SendInput.argtypes = (wintypes.UINT, ctypes.POINTER(INPUT), ctypes.c_int)
|
|
138
|
+
|
|
139
|
+
# Helpers for Get/SetWindowLongPtr
|
|
140
|
+
try:
|
|
141
|
+
GetWindowLongPtrW = user32.GetWindowLongPtrW
|
|
142
|
+
SetWindowLongPtrW = user32.SetWindowLongPtrW
|
|
143
|
+
GetWindowLongPtrW.restype = LONG_PTR
|
|
144
|
+
GetWindowLongPtrW.argtypes = (wintypes.HWND, ctypes.c_int)
|
|
145
|
+
SetWindowLongPtrW.restype = LONG_PTR
|
|
146
|
+
SetWindowLongPtrW.argtypes = (wintypes.HWND, ctypes.c_int, LONG_PTR)
|
|
147
|
+
_use_long_ptr = True
|
|
148
|
+
except AttributeError:
|
|
149
|
+
GetWindowLongW = user32.GetWindowLongW
|
|
150
|
+
SetWindowLongW = user32.SetWindowLongW
|
|
151
|
+
GetWindowLongW.restype = ctypes.c_long
|
|
152
|
+
GetWindowLongW.argtypes = (wintypes.HWND, ctypes.c_int)
|
|
153
|
+
SetWindowLongW.restype = ctypes.c_long
|
|
154
|
+
SetWindowLongW.argtypes = (wintypes.HWND, ctypes.c_int, ctypes.c_long)
|
|
155
|
+
_use_long_ptr = False
|
|
156
|
+
|
|
157
|
+
def _get_window_text(hwnd: int) -> str:
|
|
158
|
+
buf = ctypes.create_unicode_buffer(512)
|
|
159
|
+
user32.GetWindowTextW(wintypes.HWND(hwnd), buf, 512)
|
|
160
|
+
return buf.value or ""
|
|
161
|
+
|
|
162
|
+
def _get_class_name(hwnd: int) -> str:
|
|
163
|
+
buf = ctypes.create_unicode_buffer(256)
|
|
164
|
+
user32.GetClassNameW(wintypes.HWND(hwnd), buf, 256)
|
|
165
|
+
return buf.value or ""
|
|
166
|
+
|
|
167
|
+
def _get_pid_exe(hwnd: int) -> Tuple[int, Optional[str]]:
|
|
168
|
+
pid = wintypes.DWORD()
|
|
169
|
+
user32.GetWindowThreadProcessId(wintypes.HWND(hwnd), ctypes.byref(pid))
|
|
170
|
+
exe_path = None
|
|
171
|
+
access = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
|
|
172
|
+
hProcess = kernel32.OpenProcess(access, False, pid.value)
|
|
173
|
+
if hProcess:
|
|
174
|
+
try:
|
|
175
|
+
buf_len = wintypes.DWORD(4096)
|
|
176
|
+
buf = ctypes.create_unicode_buffer(4096)
|
|
177
|
+
QueryFullProcessImageNameW = getattr(kernel32, "QueryFullProcessImageNameW", None)
|
|
178
|
+
if QueryFullProcessImageNameW:
|
|
179
|
+
if QueryFullProcessImageNameW(hProcess, 0, buf, ctypes.byref(buf_len)):
|
|
180
|
+
exe_path = buf.value
|
|
181
|
+
if not exe_path:
|
|
182
|
+
GetModuleFileNameExW = getattr(psapi, "GetModuleFileNameExW", None)
|
|
183
|
+
if GetModuleFileNameExW:
|
|
184
|
+
if GetModuleFileNameExW(hProcess, 0, buf, 4096):
|
|
185
|
+
exe_path = buf.value
|
|
186
|
+
finally:
|
|
187
|
+
kernel32.CloseHandle(hProcess)
|
|
188
|
+
return pid.value, exe_path
|
|
189
|
+
|
|
190
|
+
def _get_rect(hwnd: int) -> Dict:
|
|
191
|
+
rect = wintypes.RECT()
|
|
192
|
+
user32.GetWindowRect(wintypes.HWND(hwnd), ctypes.byref(rect))
|
|
193
|
+
width = rect.right - rect.left
|
|
194
|
+
height = rect.bottom - rect.top
|
|
195
|
+
return {"left": rect.left, "top": rect.top, "right": rect.right, "bottom": rect.bottom,
|
|
196
|
+
"width": width, "height": height}
|
|
197
|
+
|
|
198
|
+
def _is_visible(hwnd: int) -> bool:
|
|
199
|
+
return bool(user32.IsWindowVisible(wintypes.HWND(hwnd)))
|
|
200
|
+
|
|
201
|
+
def _is_minimized(hwnd: int) -> bool:
|
|
202
|
+
return bool(user32.IsIconic(wintypes.HWND(hwnd)))
|
|
203
|
+
|
|
204
|
+
class WinAPI:
|
|
205
|
+
def enum_windows(self, visible_only: bool = True) -> List[Dict]:
|
|
206
|
+
items: List[Dict] = []
|
|
207
|
+
|
|
208
|
+
@EnumWindowsProc
|
|
209
|
+
def callback(hWnd, lParam):
|
|
210
|
+
try:
|
|
211
|
+
if visible_only and not _is_visible(hWnd):
|
|
212
|
+
return True
|
|
213
|
+
title = _get_window_text(hWnd)
|
|
214
|
+
cls = _get_class_name(hWnd)
|
|
215
|
+
pid, exe = _get_pid_exe(hWnd)
|
|
216
|
+
rect = _get_rect(hWnd)
|
|
217
|
+
minimized = _is_minimized(hWnd)
|
|
218
|
+
items.append({
|
|
219
|
+
"hwnd": int(hWnd),
|
|
220
|
+
"title": title,
|
|
221
|
+
"class_name": cls,
|
|
222
|
+
"pid": pid,
|
|
223
|
+
"exe": exe or "",
|
|
224
|
+
"rect": rect,
|
|
225
|
+
"is_visible": _is_visible(hWnd),
|
|
226
|
+
"is_minimized": minimized,
|
|
227
|
+
})
|
|
228
|
+
except Exception:
|
|
229
|
+
return True
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
user32.EnumWindows(callback, 0)
|
|
233
|
+
return items
|
|
234
|
+
|
|
235
|
+
def enum_child_windows(self, parent_hwnd: int) -> List[Dict]:
|
|
236
|
+
items: List[Dict] = []
|
|
237
|
+
|
|
238
|
+
@EnumChildProc
|
|
239
|
+
def callback(hChild, lParam):
|
|
240
|
+
try:
|
|
241
|
+
title = _get_window_text(hChild)
|
|
242
|
+
cls = _get_class_name(hChild)
|
|
243
|
+
pid, exe = _get_pid_exe(hChild)
|
|
244
|
+
rect = _get_rect(hChild)
|
|
245
|
+
items.append({
|
|
246
|
+
"hwnd": int(hChild),
|
|
247
|
+
"title": title,
|
|
248
|
+
"class_name": cls,
|
|
249
|
+
"pid": pid,
|
|
250
|
+
"exe": exe or "",
|
|
251
|
+
"rect": rect,
|
|
252
|
+
"is_visible": _is_visible(hChild),
|
|
253
|
+
"is_minimized": _is_minimized(hChild),
|
|
254
|
+
})
|
|
255
|
+
except Exception:
|
|
256
|
+
return True
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
user32.EnumChildWindows(wintypes.HWND(parent_hwnd), callback, 0)
|
|
260
|
+
return items
|
|
261
|
+
|
|
262
|
+
def is_window(self, hwnd: int) -> bool:
|
|
263
|
+
return bool(user32.IsWindow(wintypes.HWND(hwnd)))
|
|
264
|
+
|
|
265
|
+
def get_window_info(self, hwnd: int) -> Dict:
|
|
266
|
+
return {
|
|
267
|
+
"hwnd": int(hwnd),
|
|
268
|
+
"title": _get_window_text(hwnd),
|
|
269
|
+
"class_name": _get_class_name(hwnd),
|
|
270
|
+
"pid": _get_pid_exe(hwnd)[0],
|
|
271
|
+
"exe": _get_pid_exe(hwnd)[1] or "",
|
|
272
|
+
"rect": _get_rect(hwnd),
|
|
273
|
+
"is_visible": _is_visible(hwnd),
|
|
274
|
+
"is_minimized": _is_minimized(hwnd),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
def bring_to_foreground(self, hwnd: int) -> Tuple[bool, str]:
|
|
278
|
+
if _is_minimized(hwnd):
|
|
279
|
+
user32.ShowWindow(wintypes.HWND(hwnd), SW_RESTORE)
|
|
280
|
+
if user32.SetForegroundWindow(wintypes.HWND(hwnd)):
|
|
281
|
+
return True, "SetForegroundWindow succeeded."
|
|
282
|
+
fg = user32.GetForegroundWindow()
|
|
283
|
+
if fg:
|
|
284
|
+
target_tid = user32.GetWindowThreadProcessId(wintypes.HWND(hwnd), None)
|
|
285
|
+
fg_tid = user32.GetWindowThreadProcessId(wintypes.HWND(fg), None)
|
|
286
|
+
cur_tid = kernel32.GetCurrentThreadId()
|
|
287
|
+
user32.AttachThreadInput(fg_tid, cur_tid, True)
|
|
288
|
+
user32.AttachThreadInput(target_tid, cur_tid, True)
|
|
289
|
+
user32.BringWindowToTop(wintypes.HWND(hwnd))
|
|
290
|
+
ok = user32.SetForegroundWindow(wintypes.HWND(hwnd))
|
|
291
|
+
user32.AttachThreadInput(fg_tid, cur_tid, False)
|
|
292
|
+
user32.AttachThreadInput(target_tid, cur_tid, False)
|
|
293
|
+
if ok:
|
|
294
|
+
return True, "Foreground set via AttachThreadInput."
|
|
295
|
+
return False, "Could not bring window to foreground (OS prevented focus change)."
|
|
296
|
+
|
|
297
|
+
def move_resize(self, hwnd: int,
|
|
298
|
+
x: Optional[int], y: Optional[int],
|
|
299
|
+
width: Optional[int], height: Optional[int]) -> Tuple[bool, str]:
|
|
300
|
+
rect = _get_rect(hwnd)
|
|
301
|
+
new_x = rect["left"] if x is None else int(x)
|
|
302
|
+
new_y = rect["top"] if y is None else int(y)
|
|
303
|
+
new_w = rect["width"] if width is None else max(1, int(width))
|
|
304
|
+
new_h = rect["height"] if height is None else max(1, int(height))
|
|
305
|
+
ok = user32.SetWindowPos(wintypes.HWND(hwnd), HWND_TOP, new_x, new_y, new_w, new_h, 0)
|
|
306
|
+
return (ok != 0), f"Moved to ({new_x},{new_y}) size ({new_w}x{new_h})."
|
|
307
|
+
|
|
308
|
+
def show_window(self, hwnd: int, state: str) -> Tuple[bool, str]:
|
|
309
|
+
m = {
|
|
310
|
+
"minimize": SW_SHOWMINIMIZED,
|
|
311
|
+
"maximize": SW_SHOWMAXIMIZED,
|
|
312
|
+
"restore": SW_RESTORE,
|
|
313
|
+
"show": SW_SHOWNORMAL,
|
|
314
|
+
"hide": SW_HIDE,
|
|
315
|
+
}
|
|
316
|
+
if state not in m:
|
|
317
|
+
return False, f"Invalid state: {state}"
|
|
318
|
+
ok = user32.ShowWindow(wintypes.HWND(hwnd), m[state])
|
|
319
|
+
return (ok != 0), f"ShowWindow {state}"
|
|
320
|
+
|
|
321
|
+
def close_window(self, hwnd: int) -> Tuple[bool, str]:
|
|
322
|
+
res = user32.SendMessageTimeoutW(
|
|
323
|
+
wintypes.HWND(hwnd), WM_CLOSE, 0, 0,
|
|
324
|
+
SMTO_BLOCK | SMTO_ABORTIFHUNG, SEND_TIMEOUT, None
|
|
325
|
+
)
|
|
326
|
+
return (res != 0), "WM_CLOSE sent."
|
|
327
|
+
|
|
328
|
+
def set_topmost(self, hwnd: int, topmost: bool) -> Tuple[bool, str]:
|
|
329
|
+
flag = HWND_TOPMOST if topmost else HWND_NOTOPMOST
|
|
330
|
+
ok = user32.SetWindowPos(wintypes.HWND(hwnd), flag, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)
|
|
331
|
+
return (ok != 0), ("Topmost set" if topmost else "Topmost cleared")
|
|
332
|
+
|
|
333
|
+
def _get_exstyle(self, hwnd: int) -> int:
|
|
334
|
+
if '_use_long_ptr' in globals() and _use_long_ptr:
|
|
335
|
+
return int(GetWindowLongPtrW(wintypes.HWND(hwnd), GWL_EXSTYLE))
|
|
336
|
+
return int(GetWindowLongW(wintypes.HWND(hwnd), GWL_EXSTYLE))
|
|
337
|
+
|
|
338
|
+
def _set_exstyle(self, hwnd: int, val: int) -> int:
|
|
339
|
+
if '_use_long_ptr' in globals() and _use_long_ptr:
|
|
340
|
+
return int(SetWindowLongPtrW(wintypes.HWND(hwnd), GWL_EXSTYLE, val))
|
|
341
|
+
return int(SetWindowLongW(wintypes.HWND(hwnd), GWL_EXSTYLE, val))
|
|
342
|
+
|
|
343
|
+
def set_opacity(self, hwnd: int, alpha: int) -> Tuple[bool, str]:
|
|
344
|
+
ex = self._get_exstyle(hwnd)
|
|
345
|
+
if (ex & WS_EX_LAYERED) == 0:
|
|
346
|
+
self._set_exstyle(hwnd, ex | WS_EX_LAYERED)
|
|
347
|
+
ok = user32.SetLayeredWindowAttributes(wintypes.HWND(hwnd), 0, wintypes.BYTE(alpha), LWA_ALPHA)
|
|
348
|
+
return (ok != 0), f"Opacity set to alpha={alpha}"
|
|
349
|
+
|
|
350
|
+
def get_foreground_window(self) -> Optional[int]:
|
|
351
|
+
h = user32.GetForegroundWindow()
|
|
352
|
+
return int(h) if h else None
|
|
353
|
+
|
|
354
|
+
def get_window_rect(self, hwnd: int) -> Dict:
|
|
355
|
+
return _get_rect(hwnd)
|
|
356
|
+
|
|
357
|
+
def get_cursor_pos(self) -> Tuple[int, int]:
|
|
358
|
+
pt = wintypes.POINT()
|
|
359
|
+
user32.GetCursorPos(ctypes.byref(pt))
|
|
360
|
+
return pt.x, pt.y
|
|
361
|
+
|
|
362
|
+
def set_cursor_pos(self, x: int, y: int) -> bool:
|
|
363
|
+
return bool(user32.SetCursorPos(int(x), int(y)))
|
|
364
|
+
|
|
365
|
+
class InputSender:
|
|
366
|
+
def __init__(self):
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
def _send_input(self, inputs):
|
|
370
|
+
n = len(inputs)
|
|
371
|
+
arr = (INPUT * n)(*inputs)
|
|
372
|
+
sent = user32.SendInput(n, arr, ctypes.sizeof(INPUT))
|
|
373
|
+
return sent == n
|
|
374
|
+
|
|
375
|
+
# ---- Keyboard ----
|
|
376
|
+
def send_unicode_text(self, text: str, per_char_delay_ms: int = 2):
|
|
377
|
+
for ch in text:
|
|
378
|
+
code = ord(ch)
|
|
379
|
+
down = INPUT(type=INPUT_KEYBOARD, union=INPUT_union(ki=KEYBDINPUT(wVk=0, wScan=code, dwFlags=KEYEVENTF_UNICODE, time=0, dwExtraInfo=0)))
|
|
380
|
+
up = INPUT(type=INPUT_KEYBOARD, union=INPUT_union(ki=KEYBDINPUT(wVk=0, wScan=code, dwFlags=KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, time=0, dwExtraInfo=0)))
|
|
381
|
+
self._send_input([down, up])
|
|
382
|
+
if per_char_delay_ms > 0:
|
|
383
|
+
kernel32.Sleep(per_char_delay_ms)
|
|
384
|
+
|
|
385
|
+
def send_keys(self, keys: List[str], hold_ms: int = 50, gap_ms: int = 30) -> Tuple[bool, str]:
|
|
386
|
+
tokens = [str(k).strip().upper() for k in keys if str(k).strip()]
|
|
387
|
+
if not tokens:
|
|
388
|
+
return False, "No key tokens."
|
|
389
|
+
modifiers = [t for t in tokens if t in ("CTRL", "ALT", "SHIFT", "WIN")]
|
|
390
|
+
normals = [t for t in tokens if t not in modifiers]
|
|
391
|
+
|
|
392
|
+
def mk_down(vk):
|
|
393
|
+
return INPUT(type=INPUT_KEYBOARD, union=INPUT_union(ki=KEYBDINPUT(wVk=vk, wScan=0, dwFlags=0, time=0, dwExtraInfo=0)))
|
|
394
|
+
|
|
395
|
+
def mk_up(vk):
|
|
396
|
+
return INPUT(type=INPUT_KEYBOARD, union=INPUT_union(ki=KEYBDINPUT(wVk=vk, wScan=0, dwFlags=KEYEVENTF_KEYUP, time=0, dwExtraInfo=0)))
|
|
397
|
+
|
|
398
|
+
for mod in modifiers:
|
|
399
|
+
vk = VK.get(mod)
|
|
400
|
+
if vk is None: return False, f"Unsupported modifier: {mod}"
|
|
401
|
+
if not self._send_input([mk_down(vk)]):
|
|
402
|
+
return False, f"Failed to press modifier: {mod}"
|
|
403
|
+
|
|
404
|
+
for key in normals:
|
|
405
|
+
vk = None
|
|
406
|
+
if key in VK:
|
|
407
|
+
vk = VK[key]
|
|
408
|
+
elif len(key) == 1:
|
|
409
|
+
c = key.upper()
|
|
410
|
+
if 'A' <= c <= 'Z' or '0' <= c <= '9':
|
|
411
|
+
vk = ord(c)
|
|
412
|
+
if vk is None:
|
|
413
|
+
for mod in reversed(modifiers):
|
|
414
|
+
vkmod = VK.get(mod)
|
|
415
|
+
if vkmod is not None:
|
|
416
|
+
self._send_input([mk_up(vkmod)])
|
|
417
|
+
return False, f"Unsupported key token: {key}"
|
|
418
|
+
|
|
419
|
+
if not self._send_input([mk_down(vk), mk_up(vk)]):
|
|
420
|
+
for mod in reversed(modifiers):
|
|
421
|
+
vkmod = VK.get(mod)
|
|
422
|
+
if vkmod is not None:
|
|
423
|
+
self._send_input([mk_up(vkmod)])
|
|
424
|
+
return False, f"Failed to tap: {key}"
|
|
425
|
+
if gap_ms > 0:
|
|
426
|
+
kernel32.Sleep(gap_ms)
|
|
427
|
+
|
|
428
|
+
for mod in reversed(modifiers):
|
|
429
|
+
vk = VK.get(mod)
|
|
430
|
+
if vk is not None:
|
|
431
|
+
self._send_input([mk_up(vk)])
|
|
432
|
+
|
|
433
|
+
return True, "Keys sent."
|
|
434
|
+
|
|
435
|
+
# ---- Mouse ----
|
|
436
|
+
def _mouse_event(self, flags: int):
|
|
437
|
+
mi = MOUSEINPUT(dx=0, dy=0, mouseData=0, dwFlags=flags, time=0, dwExtraInfo=0)
|
|
438
|
+
inp = INPUT(type=INPUT_MOUSE, union=INPUT_union(mi=mi))
|
|
439
|
+
return self._send_input([inp])
|
|
440
|
+
|
|
441
|
+
def move_cursor(self, x: int, y: int) -> bool:
|
|
442
|
+
return bool(user32.SetCursorPos(int(x), int(y)))
|
|
443
|
+
|
|
444
|
+
def click_at(self, x: int, y: int, button: str = "left", double: bool = False) -> Tuple[bool, str]:
|
|
445
|
+
self.move_cursor(x, y)
|
|
446
|
+
btn = (button or "left").lower()
|
|
447
|
+
if btn == "left":
|
|
448
|
+
down, up = MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP
|
|
449
|
+
elif btn == "right":
|
|
450
|
+
down, up = MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP
|
|
451
|
+
elif btn == "middle":
|
|
452
|
+
down, up = MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP
|
|
453
|
+
else:
|
|
454
|
+
return False, f"Unsupported button: {button}"
|
|
455
|
+
if not self._mouse_event(down) or not self._mouse_event(up):
|
|
456
|
+
return False, "Mouse click failed."
|
|
457
|
+
if double:
|
|
458
|
+
kernel32.Sleep(80)
|
|
459
|
+
if not self._mouse_event(down) or not self._mouse_event(up):
|
|
460
|
+
return False, "Mouse double-click failed."
|
|
461
|
+
return True, "Click sent."
|
|
462
|
+
|
|
463
|
+
def drag_and_drop(self, x1: int, y1: int, x2: int, y2: int, steps: int = 20, step_delay_ms: int = 10) -> Tuple[bool, str]:
|
|
464
|
+
self.move_cursor(x1, y1)
|
|
465
|
+
if not self._mouse_event(MOUSEEVENTF_LEFTDOWN):
|
|
466
|
+
return False, "Mouse down failed."
|
|
467
|
+
try:
|
|
468
|
+
if steps < 1:
|
|
469
|
+
steps = 1
|
|
470
|
+
dx = (x2 - x1) / float(steps)
|
|
471
|
+
dy = (y2 - y1) / float(steps)
|
|
472
|
+
cx, cy = float(x1), float(y1)
|
|
473
|
+
for i in range(steps):
|
|
474
|
+
cx += dx
|
|
475
|
+
cy += dy
|
|
476
|
+
self.move_cursor(int(cx), int(cy))
|
|
477
|
+
if step_delay_ms > 0:
|
|
478
|
+
kernel32.Sleep(step_delay_ms)
|
|
479
|
+
finally:
|
|
480
|
+
self._mouse_event(MOUSEEVENTF_LEFTUP)
|
|
481
|
+
return True, "Drag-and-drop completed."
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.23 07:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Slot, Signal
|
|
@@ -44,9 +44,75 @@ class Worker(BaseWorker):
|
|
|
44
44
|
if (item["cmd"] in self.plugin.allowed_cmds
|
|
45
45
|
and (self.plugin.has_cmd(item["cmd"]) or 'force' in item)):
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
cmd = item["cmd"]
|
|
48
|
+
params = item.get("params", {}) or {}
|
|
49
|
+
|
|
50
|
+
# System command
|
|
51
|
+
if cmd == "sys_exec":
|
|
48
52
|
response = self.cmd_sys_exec(item)
|
|
49
53
|
|
|
54
|
+
# WinAPI commands
|
|
55
|
+
elif cmd == "win_list":
|
|
56
|
+
response = self._wrap(item, self.plugin.runner.win_list, params)
|
|
57
|
+
elif cmd == "win_find":
|
|
58
|
+
response = self._wrap(item, self.plugin.runner.win_find, params)
|
|
59
|
+
elif cmd == "win_children":
|
|
60
|
+
response = self._wrap(item, self.plugin.runner.win_children, params)
|
|
61
|
+
elif cmd == "win_foreground":
|
|
62
|
+
response = self._wrap(item, self.plugin.runner.win_foreground, params)
|
|
63
|
+
elif cmd == "win_rect":
|
|
64
|
+
response = self._wrap(item, self.plugin.runner.win_rect, params)
|
|
65
|
+
elif cmd == "win_get_state":
|
|
66
|
+
response = self._wrap(item, self.plugin.runner.win_get_state, params)
|
|
67
|
+
|
|
68
|
+
elif cmd == "win_focus":
|
|
69
|
+
response = self._wrap(item, self.plugin.runner.win_focus, params)
|
|
70
|
+
elif cmd == "win_move_resize":
|
|
71
|
+
response = self._wrap(item, self.plugin.runner.win_move_resize, params)
|
|
72
|
+
elif cmd == "win_minimize":
|
|
73
|
+
response = self._wrap(item, self.plugin.runner.win_minimize, params)
|
|
74
|
+
elif cmd == "win_maximize":
|
|
75
|
+
response = self._wrap(item, self.plugin.runner.win_maximize, params)
|
|
76
|
+
elif cmd == "win_restore":
|
|
77
|
+
response = self._wrap(item, self.plugin.runner.win_restore, params)
|
|
78
|
+
elif cmd == "win_close":
|
|
79
|
+
response = self._wrap(item, self.plugin.runner.win_close, params)
|
|
80
|
+
elif cmd == "win_show":
|
|
81
|
+
response = self._wrap(item, self.plugin.runner.win_show, params)
|
|
82
|
+
elif cmd == "win_hide":
|
|
83
|
+
response = self._wrap(item, self.plugin.runner.win_hide, params)
|
|
84
|
+
elif cmd == "win_always_on_top":
|
|
85
|
+
response = self._wrap(item, self.plugin.runner.win_always_on_top, params)
|
|
86
|
+
elif cmd == "win_set_opacity":
|
|
87
|
+
response = self._wrap(item, self.plugin.runner.win_set_opacity, params)
|
|
88
|
+
|
|
89
|
+
elif cmd == "win_screenshot":
|
|
90
|
+
response = self._wrap(item, self.plugin.runner.win_screenshot, params)
|
|
91
|
+
elif cmd == "win_area_screenshot":
|
|
92
|
+
response = self._wrap(item, self.plugin.runner.win_area_screenshot, params)
|
|
93
|
+
|
|
94
|
+
elif cmd == "win_clipboard_get":
|
|
95
|
+
response = self._wrap(item, self.plugin.runner.win_clipboard_get, params)
|
|
96
|
+
elif cmd == "win_clipboard_set":
|
|
97
|
+
response = self._wrap(item, self.plugin.runner.win_clipboard_set, params)
|
|
98
|
+
|
|
99
|
+
elif cmd == "win_cursor_get":
|
|
100
|
+
response = self._wrap(item, self.plugin.runner.win_cursor_get, params)
|
|
101
|
+
elif cmd == "win_cursor_set":
|
|
102
|
+
response = self._wrap(item, self.plugin.runner.win_cursor_set, params)
|
|
103
|
+
|
|
104
|
+
elif cmd == "win_keys_text":
|
|
105
|
+
response = self._wrap(item, self.plugin.runner.win_keys_text, params)
|
|
106
|
+
elif cmd == "win_keys_send":
|
|
107
|
+
response = self._wrap(item, self.plugin.runner.win_keys_send, params)
|
|
108
|
+
elif cmd == "win_click":
|
|
109
|
+
response = self._wrap(item, self.plugin.runner.win_click, params)
|
|
110
|
+
elif cmd == "win_drag":
|
|
111
|
+
response = self._wrap(item, self.plugin.runner.win_drag, params)
|
|
112
|
+
|
|
113
|
+
elif cmd == "win_monitors":
|
|
114
|
+
response = self._wrap(item, self.plugin.runner.win_monitors, params)
|
|
115
|
+
|
|
50
116
|
if response:
|
|
51
117
|
responses.append(response)
|
|
52
118
|
|
|
@@ -69,9 +135,6 @@ class Worker(BaseWorker):
|
|
|
69
135
|
def cmd_sys_exec(self, item: dict) -> dict:
|
|
70
136
|
"""
|
|
71
137
|
Execute system command
|
|
72
|
-
|
|
73
|
-
:param item: command item
|
|
74
|
-
:return: response item
|
|
75
138
|
"""
|
|
76
139
|
request = self.from_request(item)
|
|
77
140
|
try:
|
|
@@ -93,13 +156,20 @@ class Worker(BaseWorker):
|
|
|
93
156
|
extra = self.prepare_extra(item, result)
|
|
94
157
|
return self.make_response(item, result, extra=extra)
|
|
95
158
|
|
|
159
|
+
def _wrap(self, item: dict, fn, params: dict) -> dict:
|
|
160
|
+
"""Execute runner function and adapt to response format"""
|
|
161
|
+
request = self.from_request(item)
|
|
162
|
+
try:
|
|
163
|
+
result = fn(**(params or {}))
|
|
164
|
+
except Exception as e:
|
|
165
|
+
result = self.throw_error(e)
|
|
166
|
+
|
|
167
|
+
extra = self.prepare_extra(item, result)
|
|
168
|
+
return self.make_response(item, result, extra=extra)
|
|
169
|
+
|
|
96
170
|
def prepare_extra(self, item: dict, result: dict) -> dict:
|
|
97
171
|
"""
|
|
98
172
|
Prepare extra data for response
|
|
99
|
-
|
|
100
|
-
:param item: command item
|
|
101
|
-
:param result: response data
|
|
102
|
-
:return: extra data
|
|
103
173
|
"""
|
|
104
174
|
cmd = item["cmd"]
|
|
105
175
|
extra = {
|
|
@@ -107,19 +177,22 @@ class Worker(BaseWorker):
|
|
|
107
177
|
'cmd': cmd,
|
|
108
178
|
'code': {}
|
|
109
179
|
}
|
|
110
|
-
|
|
180
|
+
# Language hint for renderer
|
|
111
181
|
if cmd in ["sys_exec"]:
|
|
112
182
|
lang = "bash"
|
|
183
|
+
elif cmd in ["win_keys_text"]:
|
|
184
|
+
lang = "text"
|
|
185
|
+
else:
|
|
186
|
+
lang = "json"
|
|
187
|
+
|
|
113
188
|
if "params" in item and "code" in item["params"]:
|
|
114
189
|
extra["code"]["input"] = {}
|
|
115
190
|
extra["code"]["input"]["lang"] = lang
|
|
116
191
|
extra["code"]["input"]["content"] = str(item["params"]["code"])
|
|
117
|
-
if "result" in result:
|
|
192
|
+
if isinstance(result, dict) and "result" in result:
|
|
118
193
|
extra["code"]["output"] = {}
|
|
119
194
|
extra["code"]["output"]["lang"] = lang
|
|
120
195
|
extra["code"]["output"]["content"] = str(result["result"])
|
|
121
|
-
if "context" in result:
|
|
196
|
+
if isinstance(result, dict) and "context" in result:
|
|
122
197
|
extra["context"] = str(result["context"])
|
|
123
|
-
return extra
|
|
124
|
-
|
|
125
|
-
|
|
198
|
+
return extra
|
|
@@ -21,6 +21,9 @@ class BaseAgent:
|
|
|
21
21
|
self.type = ""
|
|
22
22
|
self.mode = ""
|
|
23
23
|
self.name = ""
|
|
24
|
+
self.custom_id = None
|
|
25
|
+
self.custom_options = None
|
|
26
|
+
self.custom_schema = None
|
|
24
27
|
|
|
25
28
|
def get_mode(self) -> str:
|
|
26
29
|
"""
|
|
@@ -44,12 +47,39 @@ class BaseAgent:
|
|
|
44
47
|
"""
|
|
45
48
|
pass
|
|
46
49
|
|
|
50
|
+
def set_id(self, id: str):
|
|
51
|
+
"""
|
|
52
|
+
Set custom ID for the agent
|
|
53
|
+
|
|
54
|
+
:param id: Custom ID
|
|
55
|
+
"""
|
|
56
|
+
self.custom_id = id
|
|
57
|
+
|
|
58
|
+
def set_schema(self, schema: list):
|
|
59
|
+
"""
|
|
60
|
+
Set custom schema for the agent
|
|
61
|
+
|
|
62
|
+
:param schema: Custom schema
|
|
63
|
+
"""
|
|
64
|
+
self.custom_schema = schema
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def set_options(self, options: dict):
|
|
68
|
+
"""
|
|
69
|
+
Set custom options for the agent
|
|
70
|
+
|
|
71
|
+
:param options: Custom options
|
|
72
|
+
"""
|
|
73
|
+
self.custom_options = options
|
|
74
|
+
|
|
47
75
|
def get_options(self) -> dict:
|
|
48
76
|
"""
|
|
49
77
|
Return Agent options
|
|
50
78
|
|
|
51
79
|
:return: Agent options
|
|
52
80
|
"""
|
|
81
|
+
if self.custom_options is not None:
|
|
82
|
+
return self.custom_options
|
|
53
83
|
return {}
|
|
54
84
|
|
|
55
85
|
async def run(
|
|
@@ -89,9 +119,10 @@ class BaseAgent:
|
|
|
89
119
|
print("No preset provided, returning default option value")
|
|
90
120
|
return None
|
|
91
121
|
extra = preset.extra
|
|
92
|
-
|
|
122
|
+
agent_id = self.custom_id if self.custom_id is not None else self.id
|
|
123
|
+
if not isinstance(extra, dict) or agent_id not in extra:
|
|
93
124
|
return self.get_default(section, key)
|
|
94
|
-
options = extra[
|
|
125
|
+
options = extra[agent_id]
|
|
95
126
|
if section not in options:
|
|
96
127
|
return self.get_default(section, key)
|
|
97
128
|
if key not in options[section]:
|