pygpt-net 2.6.57__py3-none-any.whl → 2.6.59__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. pygpt_net/CHANGELOG.txt +10 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +30 -25
  4. pygpt_net/controller/debug/debug.py +3 -3
  5. pygpt_net/controller/dialogs/info.py +6 -2
  6. pygpt_net/controller/ui/tabs.py +17 -0
  7. pygpt_net/core/agents/runners/llama_workflow.py +0 -0
  8. pygpt_net/core/filesystem/parser.py +37 -24
  9. pygpt_net/core/filesystem/url.py +5 -2
  10. pygpt_net/data/config/config.json +4 -3
  11. pygpt_net/data/config/models.json +3 -3
  12. pygpt_net/data/config/settings.json +41 -2
  13. pygpt_net/data/js/app/ui.js +1 -1
  14. pygpt_net/data/js/app.min.js +2 -2
  15. pygpt_net/data/locale/locale.de.ini +5 -1
  16. pygpt_net/data/locale/locale.en.ini +5 -1
  17. pygpt_net/data/locale/locale.es.ini +5 -1
  18. pygpt_net/data/locale/locale.fr.ini +5 -1
  19. pygpt_net/data/locale/locale.it.ini +5 -1
  20. pygpt_net/data/locale/locale.pl.ini +5 -1
  21. pygpt_net/data/locale/locale.uk.ini +5 -1
  22. pygpt_net/data/locale/locale.zh.ini +5 -1
  23. pygpt_net/data/locale/plugin.cmd_system.en.ini +68 -0
  24. pygpt_net/js_rc.py +5 -5
  25. pygpt_net/plugin/base/plugin.py +3 -5
  26. pygpt_net/plugin/cmd_system/config.py +377 -1
  27. pygpt_net/plugin/cmd_system/plugin.py +52 -8
  28. pygpt_net/plugin/cmd_system/runner.py +508 -32
  29. pygpt_net/plugin/cmd_system/winapi.py +481 -0
  30. pygpt_net/plugin/cmd_system/worker.py +88 -15
  31. pygpt_net/provider/agents/llama_index/workflow/supervisor.py +0 -0
  32. pygpt_net/provider/core/config/patch.py +8 -1
  33. pygpt_net/provider/llms/openai.py +6 -4
  34. pygpt_net/tools/code_interpreter/ui/html.py +2 -1
  35. pygpt_net/tools/html_canvas/ui/widgets.py +19 -18
  36. pygpt_net/tools/web_browser/__init__.py +12 -0
  37. pygpt_net/tools/web_browser/tool.py +232 -0
  38. pygpt_net/tools/web_browser/ui/__init__.py +0 -0
  39. pygpt_net/tools/web_browser/ui/dialogs.py +123 -0
  40. pygpt_net/tools/web_browser/ui/widgets.py +351 -0
  41. pygpt_net/ui/widget/textarea/html.py +172 -24
  42. pygpt_net/ui/widget/textarea/web.py +1 -1
  43. {pygpt_net-2.6.57.dist-info → pygpt_net-2.6.59.dist-info}/METADATA +81 -61
  44. {pygpt_net-2.6.57.dist-info → pygpt_net-2.6.59.dist-info}/RECORD +45 -39
  45. {pygpt_net-2.6.57.dist-info → pygpt_net-2.6.59.dist-info}/LICENSE +0 -0
  46. {pygpt_net-2.6.57.dist-info → pygpt_net-2.6.59.dist-info}/WHEEL +0 -0
  47. {pygpt_net-2.6.57.dist-info → pygpt_net-2.6.59.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.08.11 14:00:00 #
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
- if item["cmd"] == "sys_exec":
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
- lang = "python"
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
@@ -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.09.17 05:00:00 #
9
+ # Updated Date: 2025.09.22 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -144,6 +144,13 @@ class Patch:
144
144
  data["api_proxy.enabled"] = True
145
145
  updated = True
146
146
 
147
+ # < 2.6.58
148
+ if old < parse_version("2.6.58"):
149
+ print("Migrating config from < 2.6.58...")
150
+ if "ctx.urls.internal" not in data:
151
+ data["ctx.urls.internal"] = False
152
+ updated = True
153
+
147
154
  # update file
148
155
  migrated = False
149
156
  if updated:
@@ -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.09.15 01:00:00 #
9
+ # Updated Date: 2025.09.22 08:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List, Dict
@@ -89,8 +89,8 @@ class OpenAILLM(BaseLLM):
89
89
  :param stream: stream mode
90
90
  :return: LLM provider instance
91
91
  """
92
- from .llama_index.openai import OpenAI as LlamaOpenAI
93
- from .llama_index.openai import OpenAIResponses as LlamaOpenAIResponses
92
+ from llama_index.llms.openai import OpenAI as LlamaOpenAI
93
+ from llama_index.llms.openai import OpenAIResponses as LlamaOpenAIResponses
94
94
  args = self.parse_args(model.llama_index, window)
95
95
  if "api_key" not in args:
96
96
  args["api_key"] = window.core.config.get("api_key", "")
@@ -98,7 +98,9 @@ class OpenAILLM(BaseLLM):
98
98
  args["model"] = model.id
99
99
 
100
100
  args = self.inject_llamaindex_http_clients(args, window.core.config)
101
- if window.core.config.get('api_use_responses_llama', False):
101
+ mode = window.core.config.get("mode")
102
+ # dont' use Responses in agent modes
103
+ if window.core.config.get('api_use_responses_llama', False) and mode == MODE_LLAMA_INDEX:
102
104
  tools = []
103
105
  tools = window.core.api.openai.remote_tools.append_to_tools(
104
106
  mode=MODE_LLAMA_INDEX,