autoglm-gui 0.3.2__py3-none-any.whl → 0.4.1__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.
- AutoGLM_GUI/__main__.py +6 -2
- AutoGLM_GUI/adb_plus/screenshot.py +1 -4
- AutoGLM_GUI/api/__init__.py +66 -0
- AutoGLM_GUI/api/agents.py +231 -0
- AutoGLM_GUI/api/control.py +111 -0
- AutoGLM_GUI/api/devices.py +29 -0
- AutoGLM_GUI/api/media.py +163 -0
- AutoGLM_GUI/schemas.py +127 -0
- AutoGLM_GUI/scrcpy_stream.py +65 -28
- AutoGLM_GUI/server.py +2 -617
- AutoGLM_GUI/state.py +33 -0
- AutoGLM_GUI/static/assets/{about-2K7DgoQw.js → about-gHEqXVMQ.js} +1 -1
- AutoGLM_GUI/static/assets/chat-6a-qTECg.js +25 -0
- AutoGLM_GUI/static/assets/{index-BynheeWl.js → index-C8KPPfxe.js} +6 -6
- AutoGLM_GUI/static/assets/index-D2-3f619.css +1 -0
- AutoGLM_GUI/static/assets/{index-Cc7aUqXq.js → index-DgzeSwgt.js} +1 -1
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/version.py +8 -0
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.1.dist-info}/METADATA +64 -9
- autoglm_gui-0.4.1.dist-info/RECORD +44 -0
- phone_agent/adb/connection.py +0 -1
- phone_agent/adb/device.py +0 -2
- phone_agent/adb/input.py +0 -1
- phone_agent/adb/screenshot.py +0 -1
- phone_agent/agent.py +1 -1
- AutoGLM_GUI/static/assets/chat-DjOHP9wp.js +0 -25
- AutoGLM_GUI/static/assets/index-CrqBLMxN.css +0 -1
- autoglm_gui-0.3.2.dist-info/RECORD +0 -36
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.1.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.1.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/schemas.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Shared Pydantic models for the AutoGLM-GUI API."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class APIModelConfig(BaseModel):
|
|
7
|
+
base_url: str | None = None
|
|
8
|
+
api_key: str | None = None
|
|
9
|
+
model_name: str | None = None
|
|
10
|
+
max_tokens: int = 3000
|
|
11
|
+
temperature: float = 0.0
|
|
12
|
+
top_p: float = 0.85
|
|
13
|
+
frequency_penalty: float = 0.2
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class APIAgentConfig(BaseModel):
|
|
17
|
+
max_steps: int = 100
|
|
18
|
+
device_id: str | None = None
|
|
19
|
+
lang: str = "cn"
|
|
20
|
+
system_prompt: str | None = None
|
|
21
|
+
verbose: bool = True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InitRequest(BaseModel):
|
|
25
|
+
model: APIModelConfig | None = Field(default=None, alias="model_config")
|
|
26
|
+
agent: APIAgentConfig | None = Field(default=None, alias="agent_config")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ChatRequest(BaseModel):
|
|
30
|
+
message: str
|
|
31
|
+
device_id: str # 设备 ID(必填)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ChatResponse(BaseModel):
|
|
35
|
+
result: str
|
|
36
|
+
steps: int
|
|
37
|
+
success: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StatusResponse(BaseModel):
|
|
41
|
+
version: str
|
|
42
|
+
initialized: bool
|
|
43
|
+
step_count: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ResetRequest(BaseModel):
|
|
47
|
+
device_id: str # 设备 ID(必填)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ScreenshotRequest(BaseModel):
|
|
51
|
+
device_id: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ScreenshotResponse(BaseModel):
|
|
55
|
+
success: bool
|
|
56
|
+
image: str # base64 encoded PNG
|
|
57
|
+
width: int
|
|
58
|
+
height: int
|
|
59
|
+
is_sensitive: bool
|
|
60
|
+
error: str | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TapRequest(BaseModel):
|
|
64
|
+
x: int
|
|
65
|
+
y: int
|
|
66
|
+
device_id: str | None = None
|
|
67
|
+
delay: float = 0.0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TapResponse(BaseModel):
|
|
71
|
+
success: bool
|
|
72
|
+
error: str | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SwipeRequest(BaseModel):
|
|
76
|
+
start_x: int
|
|
77
|
+
start_y: int
|
|
78
|
+
end_x: int
|
|
79
|
+
end_y: int
|
|
80
|
+
duration_ms: int | None = None
|
|
81
|
+
device_id: str | None = None
|
|
82
|
+
delay: float = 0.0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SwipeResponse(BaseModel):
|
|
86
|
+
success: bool
|
|
87
|
+
error: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TouchDownRequest(BaseModel):
|
|
91
|
+
x: int
|
|
92
|
+
y: int
|
|
93
|
+
device_id: str | None = None
|
|
94
|
+
delay: float = 0.0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TouchDownResponse(BaseModel):
|
|
98
|
+
success: bool
|
|
99
|
+
error: str | None = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TouchMoveRequest(BaseModel):
|
|
103
|
+
x: int
|
|
104
|
+
y: int
|
|
105
|
+
device_id: str | None = None
|
|
106
|
+
delay: float = 0.0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TouchMoveResponse(BaseModel):
|
|
110
|
+
success: bool
|
|
111
|
+
error: str | None = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TouchUpRequest(BaseModel):
|
|
115
|
+
x: int
|
|
116
|
+
y: int
|
|
117
|
+
device_id: str | None = None
|
|
118
|
+
delay: float = 0.0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TouchUpResponse(BaseModel):
|
|
122
|
+
success: bool
|
|
123
|
+
error: str | None = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class DeviceListResponse(BaseModel):
|
|
127
|
+
devices: list[dict]
|
AutoGLM_GUI/scrcpy_stream.py
CHANGED
|
@@ -104,6 +104,7 @@ class ScrcpyStreamer:
|
|
|
104
104
|
except Exception as e:
|
|
105
105
|
print(f"[ScrcpyStreamer] Failed to start: {e}")
|
|
106
106
|
import traceback
|
|
107
|
+
|
|
107
108
|
traceback.print_exc()
|
|
108
109
|
self.stop()
|
|
109
110
|
raise RuntimeError(f"Failed to start scrcpy server: {e}") from e
|
|
@@ -124,7 +125,7 @@ class ScrcpyStreamer:
|
|
|
124
125
|
# Method 2: Find and kill by PID (more reliable)
|
|
125
126
|
cmd = cmd_base + [
|
|
126
127
|
"shell",
|
|
127
|
-
"ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9"
|
|
128
|
+
"ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9",
|
|
128
129
|
]
|
|
129
130
|
process = await asyncio.create_subprocess_exec(
|
|
130
131
|
*cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
@@ -200,9 +201,7 @@ class ScrcpyStreamer:
|
|
|
200
201
|
|
|
201
202
|
# Capture stderr to see error messages
|
|
202
203
|
self.scrcpy_process = await asyncio.create_subprocess_exec(
|
|
203
|
-
*cmd,
|
|
204
|
-
stdout=subprocess.PIPE,
|
|
205
|
-
stderr=subprocess.PIPE
|
|
204
|
+
*cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
206
205
|
)
|
|
207
206
|
|
|
208
207
|
# Wait for server to start
|
|
@@ -217,12 +216,16 @@ class ScrcpyStreamer:
|
|
|
217
216
|
# Check if it's an "Address already in use" error
|
|
218
217
|
if "Address already in use" in error_msg:
|
|
219
218
|
if attempt < max_retries - 1:
|
|
220
|
-
print(
|
|
219
|
+
print(
|
|
220
|
+
f"[ScrcpyStreamer] Address in use, retrying in {retry_delay}s (attempt {attempt + 1}/{max_retries})..."
|
|
221
|
+
)
|
|
221
222
|
await self._cleanup_existing_server()
|
|
222
223
|
await asyncio.sleep(retry_delay)
|
|
223
224
|
continue
|
|
224
225
|
else:
|
|
225
|
-
raise RuntimeError(
|
|
226
|
+
raise RuntimeError(
|
|
227
|
+
f"scrcpy server failed after {max_retries} attempts: {error_msg}"
|
|
228
|
+
)
|
|
226
229
|
else:
|
|
227
230
|
raise RuntimeError(f"scrcpy server exited immediately: {error_msg}")
|
|
228
231
|
|
|
@@ -239,7 +242,9 @@ class ScrcpyStreamer:
|
|
|
239
242
|
# Increase socket buffer size for high-resolution video
|
|
240
243
|
# Default is often 64KB, but complex frames can be 200-500KB
|
|
241
244
|
try:
|
|
242
|
-
self.tcp_socket.setsockopt(
|
|
245
|
+
self.tcp_socket.setsockopt(
|
|
246
|
+
socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024
|
|
247
|
+
) # 2MB
|
|
243
248
|
print("[ScrcpyStreamer] Set socket receive buffer to 2MB")
|
|
244
249
|
except OSError as e:
|
|
245
250
|
print(f"[ScrcpyStreamer] Warning: Failed to set socket buffer size: {e}")
|
|
@@ -267,9 +272,9 @@ class ScrcpyStreamer:
|
|
|
267
272
|
|
|
268
273
|
while i < data_len - 4:
|
|
269
274
|
# Look for start codes: 0x00 0x00 0x00 0x01 or 0x00 0x00 0x01
|
|
270
|
-
if data[i:i+4] == b
|
|
275
|
+
if data[i : i + 4] == b"\x00\x00\x00\x01":
|
|
271
276
|
start_code_len = 4
|
|
272
|
-
elif data[i:i+3] == b
|
|
277
|
+
elif data[i : i + 3] == b"\x00\x00\x01":
|
|
273
278
|
start_code_len = 3
|
|
274
279
|
else:
|
|
275
280
|
i += 1
|
|
@@ -285,8 +290,10 @@ class ScrcpyStreamer:
|
|
|
285
290
|
# Find next start code to determine NAL unit size
|
|
286
291
|
next_start = nal_start + 1
|
|
287
292
|
while next_start < data_len - 3:
|
|
288
|
-
if (
|
|
289
|
-
data[next_start:next_start+
|
|
293
|
+
if (
|
|
294
|
+
data[next_start : next_start + 4] == b"\x00\x00\x00\x01"
|
|
295
|
+
or data[next_start : next_start + 3] == b"\x00\x00\x01"
|
|
296
|
+
):
|
|
290
297
|
break
|
|
291
298
|
next_start += 1
|
|
292
299
|
else:
|
|
@@ -309,7 +316,7 @@ class ScrcpyStreamer:
|
|
|
309
316
|
nal_units = self._find_nal_units(data)
|
|
310
317
|
|
|
311
318
|
for start, nal_type, size in nal_units:
|
|
312
|
-
nal_data = data[start:start+size]
|
|
319
|
+
nal_data = data[start : start + size]
|
|
313
320
|
|
|
314
321
|
if nal_type == 7: # SPS
|
|
315
322
|
# Only cache SPS if not yet locked
|
|
@@ -317,10 +324,16 @@ class ScrcpyStreamer:
|
|
|
317
324
|
# Validate: SPS should be at least 10 bytes
|
|
318
325
|
if size >= 10 and not self.cached_sps:
|
|
319
326
|
self.cached_sps = nal_data
|
|
320
|
-
hex_preview =
|
|
321
|
-
|
|
327
|
+
hex_preview = " ".join(
|
|
328
|
+
f"{b:02x}" for b in nal_data[: min(12, len(nal_data))]
|
|
329
|
+
)
|
|
330
|
+
print(
|
|
331
|
+
f"[ScrcpyStreamer] ✓ Cached complete SPS ({size} bytes): {hex_preview}..."
|
|
332
|
+
)
|
|
322
333
|
elif size < 10:
|
|
323
|
-
print(
|
|
334
|
+
print(
|
|
335
|
+
f"[ScrcpyStreamer] ✗ Skipped truncated SPS ({size} bytes, too short)"
|
|
336
|
+
)
|
|
324
337
|
|
|
325
338
|
elif nal_type == 8: # PPS
|
|
326
339
|
# Only cache PPS if not yet locked
|
|
@@ -328,10 +341,16 @@ class ScrcpyStreamer:
|
|
|
328
341
|
# Validate: PPS should be at least 6 bytes
|
|
329
342
|
if size >= 6 and not self.cached_pps:
|
|
330
343
|
self.cached_pps = nal_data
|
|
331
|
-
hex_preview =
|
|
332
|
-
|
|
344
|
+
hex_preview = " ".join(
|
|
345
|
+
f"{b:02x}" for b in nal_data[: min(12, len(nal_data))]
|
|
346
|
+
)
|
|
347
|
+
print(
|
|
348
|
+
f"[ScrcpyStreamer] ✓ Cached complete PPS ({size} bytes): {hex_preview}..."
|
|
349
|
+
)
|
|
333
350
|
elif size < 6:
|
|
334
|
-
print(
|
|
351
|
+
print(
|
|
352
|
+
f"[ScrcpyStreamer] ✗ Skipped truncated PPS ({size} bytes, too short)"
|
|
353
|
+
)
|
|
335
354
|
|
|
336
355
|
elif nal_type == 5: # IDR frame
|
|
337
356
|
# ✅ ALWAYS update IDR to keep the LATEST frame
|
|
@@ -340,7 +359,9 @@ class ScrcpyStreamer:
|
|
|
340
359
|
is_first = self.cached_idr is None
|
|
341
360
|
self.cached_idr = nal_data
|
|
342
361
|
if is_first:
|
|
343
|
-
print(
|
|
362
|
+
print(
|
|
363
|
+
f"[ScrcpyStreamer] ✓ Cached initial IDR frame ({size} bytes)"
|
|
364
|
+
)
|
|
344
365
|
# Don't log every IDR update (too verbose)
|
|
345
366
|
|
|
346
367
|
# Lock SPS/PPS once we have complete initial parameters
|
|
@@ -366,7 +387,9 @@ class ScrcpyStreamer:
|
|
|
366
387
|
return data
|
|
367
388
|
|
|
368
389
|
# Find all IDR frames
|
|
369
|
-
idr_positions = [
|
|
390
|
+
idr_positions = [
|
|
391
|
+
(start, size) for start, nal_type, size in nal_units if nal_type == 5
|
|
392
|
+
]
|
|
370
393
|
|
|
371
394
|
if not idr_positions:
|
|
372
395
|
return data
|
|
@@ -386,7 +409,9 @@ class ScrcpyStreamer:
|
|
|
386
409
|
if data[prepend_offset:idr_start] != sps_pps:
|
|
387
410
|
# Prepend SPS/PPS before this IDR
|
|
388
411
|
result.extend(sps_pps)
|
|
389
|
-
print(
|
|
412
|
+
print(
|
|
413
|
+
f"[ScrcpyStreamer] Prepended SPS/PPS before IDR at position {idr_start}"
|
|
414
|
+
)
|
|
390
415
|
|
|
391
416
|
# Update position to start of IDR
|
|
392
417
|
last_pos = idr_start
|
|
@@ -409,11 +434,17 @@ class ScrcpyStreamer:
|
|
|
409
434
|
init_data += self.cached_idr
|
|
410
435
|
|
|
411
436
|
# Validate data integrity
|
|
412
|
-
print(
|
|
413
|
-
print(
|
|
414
|
-
|
|
437
|
+
print("[ScrcpyStreamer] Returning init data:")
|
|
438
|
+
print(
|
|
439
|
+
f" - SPS: {len(self.cached_sps)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_sps[:8])}"
|
|
440
|
+
)
|
|
441
|
+
print(
|
|
442
|
+
f" - PPS: {len(self.cached_pps)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_pps[:8])}"
|
|
443
|
+
)
|
|
415
444
|
if self.cached_idr:
|
|
416
|
-
print(
|
|
445
|
+
print(
|
|
446
|
+
f" - IDR: {len(self.cached_idr)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_idr[:8])}"
|
|
447
|
+
)
|
|
417
448
|
print(f" - Total: {len(init_data)} bytes")
|
|
418
449
|
|
|
419
450
|
return init_data
|
|
@@ -442,7 +473,9 @@ class ScrcpyStreamer:
|
|
|
442
473
|
|
|
443
474
|
# Log large chunks (might indicate complex frames)
|
|
444
475
|
if len(data) > 200 * 1024: # > 200KB
|
|
445
|
-
print(
|
|
476
|
+
print(
|
|
477
|
+
f"[ScrcpyStreamer] Large chunk received: {len(data) / 1024:.1f} KB"
|
|
478
|
+
)
|
|
446
479
|
|
|
447
480
|
# Cache INITIAL complete SPS/PPS/IDR for future use
|
|
448
481
|
# (Later chunks may have truncated NAL units, so we only cache once)
|
|
@@ -457,7 +490,9 @@ class ScrcpyStreamer:
|
|
|
457
490
|
except ConnectionError:
|
|
458
491
|
raise
|
|
459
492
|
except Exception as e:
|
|
460
|
-
print(
|
|
493
|
+
print(
|
|
494
|
+
f"[ScrcpyStreamer] Unexpected error in read_h264_chunk: {type(e).__name__}: {e}"
|
|
495
|
+
)
|
|
461
496
|
raise ConnectionError(f"Failed to read from socket: {e}") from e
|
|
462
497
|
|
|
463
498
|
def stop(self) -> None:
|
|
@@ -489,7 +524,9 @@ class ScrcpyStreamer:
|
|
|
489
524
|
if self.device_id:
|
|
490
525
|
cmd.extend(["-s", self.device_id])
|
|
491
526
|
cmd.extend(["forward", "--remove", f"tcp:{self.port}"])
|
|
492
|
-
subprocess.run(
|
|
527
|
+
subprocess.run(
|
|
528
|
+
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2
|
|
529
|
+
)
|
|
493
530
|
except Exception:
|
|
494
531
|
pass
|
|
495
532
|
self.forward_cleanup_needed = False
|