autoglm-gui 0.3.2__py3-none-any.whl → 0.4.2__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 +70 -30
- AutoGLM_GUI/server.py +2 -617
- AutoGLM_GUI/state.py +33 -0
- AutoGLM_GUI/static/assets/{about-2K7DgoQw.js → about-C9954kpk.js} +1 -1
- AutoGLM_GUI/static/assets/chat-CSgek5Xo.js +25 -0
- AutoGLM_GUI/static/assets/index-7WS8sURE.css +1 -0
- AutoGLM_GUI/static/assets/{index-Cc7aUqXq.js → index-CCRJFa_5.js} +1 -1
- AutoGLM_GUI/static/assets/{index-BynheeWl.js → index-DZmNavz6.js} +6 -6
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/version.py +8 -0
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.2.dist-info}/METADATA +64 -9
- autoglm_gui-0.4.2.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.2.dist-info}/WHEEL +0 -0
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.2.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-0.3.2.dist-info → autoglm_gui-0.4.2.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
|
@@ -16,6 +16,7 @@ class ScrcpyStreamer:
|
|
|
16
16
|
max_size: int = 1280,
|
|
17
17
|
bit_rate: int = 1_000_000,
|
|
18
18
|
port: int = 27183,
|
|
19
|
+
idr_interval_s: int = 1,
|
|
19
20
|
):
|
|
20
21
|
"""Initialize ScrcpyStreamer.
|
|
21
22
|
|
|
@@ -24,11 +25,13 @@ class ScrcpyStreamer:
|
|
|
24
25
|
max_size: Maximum video dimension
|
|
25
26
|
bit_rate: Video bitrate in bps
|
|
26
27
|
port: TCP port for scrcpy socket
|
|
28
|
+
idr_interval_s: Seconds between IDR frames (controls GOP length)
|
|
27
29
|
"""
|
|
28
30
|
self.device_id = device_id
|
|
29
31
|
self.max_size = max_size
|
|
30
32
|
self.bit_rate = bit_rate
|
|
31
33
|
self.port = port
|
|
34
|
+
self.idr_interval_s = idr_interval_s
|
|
32
35
|
|
|
33
36
|
self.scrcpy_process: subprocess.Popen | None = None
|
|
34
37
|
self.tcp_socket: socket.socket | None = None
|
|
@@ -104,6 +107,7 @@ class ScrcpyStreamer:
|
|
|
104
107
|
except Exception as e:
|
|
105
108
|
print(f"[ScrcpyStreamer] Failed to start: {e}")
|
|
106
109
|
import traceback
|
|
110
|
+
|
|
107
111
|
traceback.print_exc()
|
|
108
112
|
self.stop()
|
|
109
113
|
raise RuntimeError(f"Failed to start scrcpy server: {e}") from e
|
|
@@ -124,7 +128,7 @@ class ScrcpyStreamer:
|
|
|
124
128
|
# Method 2: Find and kill by PID (more reliable)
|
|
125
129
|
cmd = cmd_base + [
|
|
126
130
|
"shell",
|
|
127
|
-
"ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9"
|
|
131
|
+
"ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9",
|
|
128
132
|
]
|
|
129
133
|
process = await asyncio.create_subprocess_exec(
|
|
130
134
|
*cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
@@ -193,16 +197,14 @@ class ScrcpyStreamer:
|
|
|
193
197
|
"audio=false",
|
|
194
198
|
"control=false",
|
|
195
199
|
"cleanup=false",
|
|
196
|
-
# Force I-frame (IDR)
|
|
197
|
-
"video_codec_options=i-frame-interval=
|
|
200
|
+
# Force I-frame (IDR) at fixed interval (GOP length) for reliable reconnection
|
|
201
|
+
f"video_codec_options=i-frame-interval={self.idr_interval_s}",
|
|
198
202
|
]
|
|
199
203
|
cmd.extend(server_args)
|
|
200
204
|
|
|
201
205
|
# Capture stderr to see error messages
|
|
202
206
|
self.scrcpy_process = await asyncio.create_subprocess_exec(
|
|
203
|
-
*cmd,
|
|
204
|
-
stdout=subprocess.PIPE,
|
|
205
|
-
stderr=subprocess.PIPE
|
|
207
|
+
*cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
206
208
|
)
|
|
207
209
|
|
|
208
210
|
# Wait for server to start
|
|
@@ -217,12 +219,16 @@ class ScrcpyStreamer:
|
|
|
217
219
|
# Check if it's an "Address already in use" error
|
|
218
220
|
if "Address already in use" in error_msg:
|
|
219
221
|
if attempt < max_retries - 1:
|
|
220
|
-
print(
|
|
222
|
+
print(
|
|
223
|
+
f"[ScrcpyStreamer] Address in use, retrying in {retry_delay}s (attempt {attempt + 1}/{max_retries})..."
|
|
224
|
+
)
|
|
221
225
|
await self._cleanup_existing_server()
|
|
222
226
|
await asyncio.sleep(retry_delay)
|
|
223
227
|
continue
|
|
224
228
|
else:
|
|
225
|
-
raise RuntimeError(
|
|
229
|
+
raise RuntimeError(
|
|
230
|
+
f"scrcpy server failed after {max_retries} attempts: {error_msg}"
|
|
231
|
+
)
|
|
226
232
|
else:
|
|
227
233
|
raise RuntimeError(f"scrcpy server exited immediately: {error_msg}")
|
|
228
234
|
|
|
@@ -239,7 +245,9 @@ class ScrcpyStreamer:
|
|
|
239
245
|
# Increase socket buffer size for high-resolution video
|
|
240
246
|
# Default is often 64KB, but complex frames can be 200-500KB
|
|
241
247
|
try:
|
|
242
|
-
self.tcp_socket.setsockopt(
|
|
248
|
+
self.tcp_socket.setsockopt(
|
|
249
|
+
socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024
|
|
250
|
+
) # 2MB
|
|
243
251
|
print("[ScrcpyStreamer] Set socket receive buffer to 2MB")
|
|
244
252
|
except OSError as e:
|
|
245
253
|
print(f"[ScrcpyStreamer] Warning: Failed to set socket buffer size: {e}")
|
|
@@ -267,9 +275,9 @@ class ScrcpyStreamer:
|
|
|
267
275
|
|
|
268
276
|
while i < data_len - 4:
|
|
269
277
|
# Look for start codes: 0x00 0x00 0x00 0x01 or 0x00 0x00 0x01
|
|
270
|
-
if data[i:i+4] == b
|
|
278
|
+
if data[i : i + 4] == b"\x00\x00\x00\x01":
|
|
271
279
|
start_code_len = 4
|
|
272
|
-
elif data[i:i+3] == b
|
|
280
|
+
elif data[i : i + 3] == b"\x00\x00\x01":
|
|
273
281
|
start_code_len = 3
|
|
274
282
|
else:
|
|
275
283
|
i += 1
|
|
@@ -285,8 +293,10 @@ class ScrcpyStreamer:
|
|
|
285
293
|
# Find next start code to determine NAL unit size
|
|
286
294
|
next_start = nal_start + 1
|
|
287
295
|
while next_start < data_len - 3:
|
|
288
|
-
if (
|
|
289
|
-
data[next_start:next_start+
|
|
296
|
+
if (
|
|
297
|
+
data[next_start : next_start + 4] == b"\x00\x00\x00\x01"
|
|
298
|
+
or data[next_start : next_start + 3] == b"\x00\x00\x01"
|
|
299
|
+
):
|
|
290
300
|
break
|
|
291
301
|
next_start += 1
|
|
292
302
|
else:
|
|
@@ -309,7 +319,7 @@ class ScrcpyStreamer:
|
|
|
309
319
|
nal_units = self._find_nal_units(data)
|
|
310
320
|
|
|
311
321
|
for start, nal_type, size in nal_units:
|
|
312
|
-
nal_data = data[start:start+size]
|
|
322
|
+
nal_data = data[start : start + size]
|
|
313
323
|
|
|
314
324
|
if nal_type == 7: # SPS
|
|
315
325
|
# Only cache SPS if not yet locked
|
|
@@ -317,10 +327,16 @@ class ScrcpyStreamer:
|
|
|
317
327
|
# Validate: SPS should be at least 10 bytes
|
|
318
328
|
if size >= 10 and not self.cached_sps:
|
|
319
329
|
self.cached_sps = nal_data
|
|
320
|
-
hex_preview =
|
|
321
|
-
|
|
330
|
+
hex_preview = " ".join(
|
|
331
|
+
f"{b:02x}" for b in nal_data[: min(12, len(nal_data))]
|
|
332
|
+
)
|
|
333
|
+
print(
|
|
334
|
+
f"[ScrcpyStreamer] ✓ Cached complete SPS ({size} bytes): {hex_preview}..."
|
|
335
|
+
)
|
|
322
336
|
elif size < 10:
|
|
323
|
-
print(
|
|
337
|
+
print(
|
|
338
|
+
f"[ScrcpyStreamer] ✗ Skipped truncated SPS ({size} bytes, too short)"
|
|
339
|
+
)
|
|
324
340
|
|
|
325
341
|
elif nal_type == 8: # PPS
|
|
326
342
|
# Only cache PPS if not yet locked
|
|
@@ -328,10 +344,16 @@ class ScrcpyStreamer:
|
|
|
328
344
|
# Validate: PPS should be at least 6 bytes
|
|
329
345
|
if size >= 6 and not self.cached_pps:
|
|
330
346
|
self.cached_pps = nal_data
|
|
331
|
-
hex_preview =
|
|
332
|
-
|
|
347
|
+
hex_preview = " ".join(
|
|
348
|
+
f"{b:02x}" for b in nal_data[: min(12, len(nal_data))]
|
|
349
|
+
)
|
|
350
|
+
print(
|
|
351
|
+
f"[ScrcpyStreamer] ✓ Cached complete PPS ({size} bytes): {hex_preview}..."
|
|
352
|
+
)
|
|
333
353
|
elif size < 6:
|
|
334
|
-
print(
|
|
354
|
+
print(
|
|
355
|
+
f"[ScrcpyStreamer] ✗ Skipped truncated PPS ({size} bytes, too short)"
|
|
356
|
+
)
|
|
335
357
|
|
|
336
358
|
elif nal_type == 5: # IDR frame
|
|
337
359
|
# ✅ ALWAYS update IDR to keep the LATEST frame
|
|
@@ -340,7 +362,9 @@ class ScrcpyStreamer:
|
|
|
340
362
|
is_first = self.cached_idr is None
|
|
341
363
|
self.cached_idr = nal_data
|
|
342
364
|
if is_first:
|
|
343
|
-
print(
|
|
365
|
+
print(
|
|
366
|
+
f"[ScrcpyStreamer] ✓ Cached initial IDR frame ({size} bytes)"
|
|
367
|
+
)
|
|
344
368
|
# Don't log every IDR update (too verbose)
|
|
345
369
|
|
|
346
370
|
# Lock SPS/PPS once we have complete initial parameters
|
|
@@ -366,7 +390,9 @@ class ScrcpyStreamer:
|
|
|
366
390
|
return data
|
|
367
391
|
|
|
368
392
|
# Find all IDR frames
|
|
369
|
-
idr_positions = [
|
|
393
|
+
idr_positions = [
|
|
394
|
+
(start, size) for start, nal_type, size in nal_units if nal_type == 5
|
|
395
|
+
]
|
|
370
396
|
|
|
371
397
|
if not idr_positions:
|
|
372
398
|
return data
|
|
@@ -386,7 +412,9 @@ class ScrcpyStreamer:
|
|
|
386
412
|
if data[prepend_offset:idr_start] != sps_pps:
|
|
387
413
|
# Prepend SPS/PPS before this IDR
|
|
388
414
|
result.extend(sps_pps)
|
|
389
|
-
print(
|
|
415
|
+
print(
|
|
416
|
+
f"[ScrcpyStreamer] Prepended SPS/PPS before IDR at position {idr_start}"
|
|
417
|
+
)
|
|
390
418
|
|
|
391
419
|
# Update position to start of IDR
|
|
392
420
|
last_pos = idr_start
|
|
@@ -409,11 +437,17 @@ class ScrcpyStreamer:
|
|
|
409
437
|
init_data += self.cached_idr
|
|
410
438
|
|
|
411
439
|
# Validate data integrity
|
|
412
|
-
print(
|
|
413
|
-
print(
|
|
414
|
-
|
|
440
|
+
print("[ScrcpyStreamer] Returning init data:")
|
|
441
|
+
print(
|
|
442
|
+
f" - SPS: {len(self.cached_sps)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_sps[:8])}"
|
|
443
|
+
)
|
|
444
|
+
print(
|
|
445
|
+
f" - PPS: {len(self.cached_pps)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_pps[:8])}"
|
|
446
|
+
)
|
|
415
447
|
if self.cached_idr:
|
|
416
|
-
print(
|
|
448
|
+
print(
|
|
449
|
+
f" - IDR: {len(self.cached_idr)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_idr[:8])}"
|
|
450
|
+
)
|
|
417
451
|
print(f" - Total: {len(init_data)} bytes")
|
|
418
452
|
|
|
419
453
|
return init_data
|
|
@@ -442,7 +476,9 @@ class ScrcpyStreamer:
|
|
|
442
476
|
|
|
443
477
|
# Log large chunks (might indicate complex frames)
|
|
444
478
|
if len(data) > 200 * 1024: # > 200KB
|
|
445
|
-
print(
|
|
479
|
+
print(
|
|
480
|
+
f"[ScrcpyStreamer] Large chunk received: {len(data) / 1024:.1f} KB"
|
|
481
|
+
)
|
|
446
482
|
|
|
447
483
|
# Cache INITIAL complete SPS/PPS/IDR for future use
|
|
448
484
|
# (Later chunks may have truncated NAL units, so we only cache once)
|
|
@@ -457,7 +493,9 @@ class ScrcpyStreamer:
|
|
|
457
493
|
except ConnectionError:
|
|
458
494
|
raise
|
|
459
495
|
except Exception as e:
|
|
460
|
-
print(
|
|
496
|
+
print(
|
|
497
|
+
f"[ScrcpyStreamer] Unexpected error in read_h264_chunk: {type(e).__name__}: {e}"
|
|
498
|
+
)
|
|
461
499
|
raise ConnectionError(f"Failed to read from socket: {e}") from e
|
|
462
500
|
|
|
463
501
|
def stop(self) -> None:
|
|
@@ -489,7 +527,9 @@ class ScrcpyStreamer:
|
|
|
489
527
|
if self.device_id:
|
|
490
528
|
cmd.extend(["-s", self.device_id])
|
|
491
529
|
cmd.extend(["forward", "--remove", f"tcp:{self.port}"])
|
|
492
|
-
subprocess.run(
|
|
530
|
+
subprocess.run(
|
|
531
|
+
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2
|
|
532
|
+
)
|
|
493
533
|
except Exception:
|
|
494
534
|
pass
|
|
495
535
|
self.forward_cleanup_needed = False
|