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/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]
@@ -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) every 1 second for reliable reconnection
197
- "video_codec_options=i-frame-interval=1",
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(f"[ScrcpyStreamer] Address in use, retrying in {retry_delay}s (attempt {attempt + 1}/{max_retries})...")
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(f"scrcpy server failed after {max_retries} attempts: {error_msg}")
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(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024) # 2MB
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'\x00\x00\x00\x01':
278
+ if data[i : i + 4] == b"\x00\x00\x00\x01":
271
279
  start_code_len = 4
272
- elif data[i:i+3] == b'\x00\x00\x01':
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 (data[next_start:next_start+4] == b'\x00\x00\x00\x01' or
289
- data[next_start:next_start+3] == b'\x00\x00\x01'):
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 = ' '.join(f'{b:02x}' for b in nal_data[:min(12, len(nal_data))])
321
- print(f"[ScrcpyStreamer] Cached complete SPS ({size} bytes): {hex_preview}...")
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(f"[ScrcpyStreamer] ✗ Skipped truncated SPS ({size} bytes, too short)")
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 = ' '.join(f'{b:02x}' for b in nal_data[:min(12, len(nal_data))])
332
- print(f"[ScrcpyStreamer] Cached complete PPS ({size} bytes): {hex_preview}...")
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(f"[ScrcpyStreamer] ✗ Skipped truncated PPS ({size} bytes, too short)")
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(f"[ScrcpyStreamer] ✓ Cached initial IDR frame ({size} bytes)")
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 = [(start, size) for start, nal_type, size in nal_units if nal_type == 5]
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(f"[ScrcpyStreamer] Prepended SPS/PPS before IDR at position {idr_start}")
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(f"[ScrcpyStreamer] Returning init data:")
413
- print(f" - SPS: {len(self.cached_sps)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_sps[:8])}")
414
- print(f" - PPS: {len(self.cached_pps)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_pps[:8])}")
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(f" - IDR: {len(self.cached_idr)} bytes, starts with {' '.join(f'{b:02x}' for b in self.cached_idr[:8])}")
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(f"[ScrcpyStreamer] Large chunk received: {len(data) / 1024:.1f} KB")
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(f"[ScrcpyStreamer] Unexpected error in read_h264_chunk: {type(e).__name__}: {e}")
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(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
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