autoglm-gui 1.4.1__py3-none-any.whl → 1.5.0__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 (104) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -4
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. AutoGLM_GUI/actions/handler.py +196 -0
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. AutoGLM_GUI/adb/__init__.py +53 -0
  7. AutoGLM_GUI/adb/apps.py +227 -0
  8. AutoGLM_GUI/adb/connection.py +323 -0
  9. AutoGLM_GUI/adb/device.py +171 -0
  10. AutoGLM_GUI/adb/input.py +67 -0
  11. AutoGLM_GUI/adb/screenshot.py +11 -0
  12. AutoGLM_GUI/adb/timing.py +167 -0
  13. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  14. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  15. AutoGLM_GUI/adb_plus/serial.py +38 -20
  16. AutoGLM_GUI/adb_plus/touch.py +4 -9
  17. AutoGLM_GUI/agents/__init__.py +43 -12
  18. AutoGLM_GUI/agents/events.py +19 -0
  19. AutoGLM_GUI/agents/factory.py +31 -38
  20. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  21. AutoGLM_GUI/agents/glm/agent.py +292 -0
  22. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  23. AutoGLM_GUI/agents/glm/parser.py +110 -0
  24. AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
  25. AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
  26. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  27. AutoGLM_GUI/agents/mai/agent.py +405 -0
  28. AutoGLM_GUI/agents/mai/parser.py +254 -0
  29. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  30. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  31. AutoGLM_GUI/agents/protocols.py +12 -8
  32. AutoGLM_GUI/agents/stream_runner.py +188 -0
  33. AutoGLM_GUI/api/__init__.py +40 -21
  34. AutoGLM_GUI/api/agents.py +157 -240
  35. AutoGLM_GUI/api/control.py +9 -6
  36. AutoGLM_GUI/api/devices.py +102 -12
  37. AutoGLM_GUI/api/history.py +78 -0
  38. AutoGLM_GUI/api/layered_agent.py +67 -15
  39. AutoGLM_GUI/api/media.py +64 -1
  40. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  41. AutoGLM_GUI/config.py +81 -0
  42. AutoGLM_GUI/config_manager.py +68 -51
  43. AutoGLM_GUI/device_manager.py +248 -29
  44. AutoGLM_GUI/device_protocol.py +1 -1
  45. AutoGLM_GUI/devices/adb_device.py +5 -10
  46. AutoGLM_GUI/devices/mock_device.py +4 -2
  47. AutoGLM_GUI/devices/remote_device.py +8 -3
  48. AutoGLM_GUI/history_manager.py +164 -0
  49. AutoGLM_GUI/i18n.py +81 -0
  50. AutoGLM_GUI/model/__init__.py +5 -0
  51. AutoGLM_GUI/model/message_builder.py +69 -0
  52. AutoGLM_GUI/model/types.py +24 -0
  53. AutoGLM_GUI/models/__init__.py +10 -0
  54. AutoGLM_GUI/models/history.py +96 -0
  55. AutoGLM_GUI/models/scheduled_task.py +71 -0
  56. AutoGLM_GUI/parsers/__init__.py +22 -0
  57. AutoGLM_GUI/parsers/base.py +50 -0
  58. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  59. AutoGLM_GUI/phone_agent_manager.py +62 -396
  60. AutoGLM_GUI/platform_utils.py +26 -0
  61. AutoGLM_GUI/prompt_config.py +15 -0
  62. AutoGLM_GUI/prompts/__init__.py +32 -0
  63. AutoGLM_GUI/scheduler_manager.py +304 -0
  64. AutoGLM_GUI/schemas.py +234 -72
  65. AutoGLM_GUI/scrcpy_stream.py +142 -24
  66. AutoGLM_GUI/socketio_server.py +100 -27
  67. AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-BQm96DAl.js} +1 -1
  68. AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
  69. AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
  70. AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
  71. AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
  72. AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
  73. AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
  74. AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-CmZSnDqc.js} +1 -1
  75. AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
  76. AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
  77. AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
  78. AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
  79. AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
  80. AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
  81. AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
  82. AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
  83. AutoGLM_GUI/static/index.html +2 -2
  84. AutoGLM_GUI/types.py +17 -0
  85. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +137 -130
  86. autoglm_gui-1.5.0.dist-info/RECORD +157 -0
  87. AutoGLM_GUI/agents/mai_adapter.py +0 -627
  88. AutoGLM_GUI/api/dual_model.py +0 -317
  89. AutoGLM_GUI/dual_model/__init__.py +0 -53
  90. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  91. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  92. AutoGLM_GUI/dual_model/protocols.py +0 -354
  93. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  94. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  95. AutoGLM_GUI/phone_agent_patches.py +0 -147
  96. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
  97. AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
  98. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
  99. AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
  100. AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
  101. autoglm_gui-1.4.1.dist-info/RECORD +0 -117
  102. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
  103. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
  104. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,6 +5,7 @@ import os
5
5
  import socket
6
6
  import subprocess
7
7
  import sys
8
+ import time
8
9
  from dataclasses import dataclass
9
10
  from pathlib import Path
10
11
  from asyncio.subprocess import Process as AsyncProcess
@@ -23,6 +24,74 @@ from AutoGLM_GUI.scrcpy_protocol import (
23
24
  )
24
25
 
25
26
 
27
+ async def is_port_available(port: int, host: str = "127.0.0.1") -> bool:
28
+ """Test if TCP port is available for binding.
29
+
30
+ Args:
31
+ port: TCP port number
32
+ host: Host address to test
33
+
34
+ Returns:
35
+ True if port can be bound (available), False otherwise
36
+ """
37
+ sock = None
38
+ try:
39
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
40
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
41
+ sock.setblocking(False)
42
+ sock.bind((host, port))
43
+ logger.debug(f"Port {port} is available for binding")
44
+ return True
45
+ except OSError as e:
46
+ # Handle cross-platform errno for "Address already in use"
47
+ # macOS: 48, Linux: 98, Windows: 10048
48
+ logger.debug(f"Port {port} is occupied: {e}")
49
+ return False
50
+ finally:
51
+ if sock:
52
+ sock.close()
53
+
54
+
55
+ async def wait_for_port_release(
56
+ port: int,
57
+ timeout: float = 5.0,
58
+ poll_interval: float = 0.2,
59
+ host: str = "127.0.0.1",
60
+ ) -> bool:
61
+ """Wait for TCP port to become available with polling.
62
+
63
+ Args:
64
+ port: TCP port to wait for
65
+ timeout: Maximum wait time in seconds (default: 5.0)
66
+ poll_interval: Check interval in seconds (default: 0.2)
67
+ host: Host address
68
+
69
+ Returns:
70
+ True if port became available, False if timeout
71
+ """
72
+ start_time = time.time()
73
+ attempt = 0
74
+
75
+ while time.time() - start_time < timeout:
76
+ attempt += 1
77
+ if await is_port_available(port, host):
78
+ elapsed = time.time() - start_time
79
+ logger.info(
80
+ f"Port {port} became available after {elapsed:.2f}s ({attempt} checks)"
81
+ )
82
+ return True
83
+
84
+ # Log progress every second for debugging
85
+ if attempt % 5 == 0: # Every 1 second (5 * 0.2s)
86
+ elapsed = time.time() - start_time
87
+ logger.debug(f"Still waiting for port {port}... ({elapsed:.1f}s elapsed)")
88
+
89
+ await asyncio.sleep(poll_interval)
90
+
91
+ logger.warning(f"Port {port} did not release within {timeout}s timeout")
92
+ return False
93
+
94
+
26
95
  @dataclass
27
96
  class ScrcpyServerOptions:
28
97
  max_size: int
@@ -159,29 +228,44 @@ class ScrcpyStreamer:
159
228
  raise RuntimeError(f"Failed to start scrcpy server: {e}") from e
160
229
 
161
230
  async def _cleanup_existing_server(self) -> None:
162
- """Kill existing scrcpy server processes on device."""
231
+ """Kill existing scrcpy server processes and wait for port release."""
163
232
  cmd_base = ["adb"]
164
233
  if self.device_id:
165
234
  cmd_base.extend(["-s", self.device_id])
166
235
 
167
236
  # Method 1: Try pkill
237
+ logger.debug("Killing scrcpy processes via pkill...")
168
238
  cmd = cmd_base + ["shell", "pkill", "-9", "-f", "app_process.*scrcpy"]
169
239
  await run_cmd_silently(cmd)
170
240
 
171
241
  # Method 2: Find and kill by PID (more reliable)
242
+ logger.debug("Killing scrcpy processes via PID...")
172
243
  cmd = cmd_base + [
173
244
  "shell",
174
245
  "ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9",
175
246
  ]
176
247
  await run_cmd_silently(cmd)
177
248
 
178
- # Method 3: Remove port forward if exists
249
+ # Method 3: Remove port forward
250
+ logger.debug(f"Removing ADB port forward on port {self.port}...")
179
251
  cmd_remove_forward = cmd_base + ["forward", "--remove", f"tcp:{self.port}"]
180
252
  await run_cmd_silently(cmd_remove_forward)
181
253
 
182
- # Wait for resources to be released
183
- logger.debug("Waiting for cleanup to complete...")
184
- await asyncio.sleep(2)
254
+ # Wait for port to be truly available (instead of fixed sleep)
255
+ logger.info(f"Waiting for port {self.port} to be released...")
256
+ port_released = await wait_for_port_release(
257
+ self.port,
258
+ timeout=5.0, # Max 5 seconds (vs old fixed 2s)
259
+ poll_interval=0.2, # Check every 200ms
260
+ )
261
+
262
+ if not port_released:
263
+ logger.warning(
264
+ f"Port {self.port} still occupied after cleanup. "
265
+ "Will attempt to start anyway (may fail)."
266
+ )
267
+ else:
268
+ logger.info(f"Port {self.port} successfully released and ready")
185
269
 
186
270
  async def _push_server(self) -> None:
187
271
  """Push scrcpy-server to device."""
@@ -221,9 +305,9 @@ class ScrcpyStreamer:
221
305
  )
222
306
 
223
307
  async def _start_server(self) -> None:
224
- """Start scrcpy server on device with retry on address conflict."""
308
+ """Start scrcpy server on device with intelligent retry."""
225
309
  max_retries = 3
226
- retry_delay = 2
310
+ retry_delay = 1.0 # Reduced from 2s (cleanup handles waiting now)
227
311
 
228
312
  options = self._build_server_options()
229
313
 
@@ -275,43 +359,77 @@ class ScrcpyStreamer:
275
359
  error_msg = stderr.decode() if stderr else stdout.decode()
276
360
 
277
361
  if error_msg is not None:
362
+ # Detailed error classification
278
363
  if "Address already in use" in error_msg:
364
+ logger.error(
365
+ f"Port {self.port} conflict detected (attempt {attempt + 1}/{max_retries}). "
366
+ f"Error: {error_msg[:200]}"
367
+ )
279
368
  if attempt < max_retries - 1:
280
369
  logger.warning(
281
- f"Address in use, retrying in {retry_delay}s (attempt {attempt + 1}/{max_retries})..."
370
+ f"Retrying with aggressive cleanup in {retry_delay}s..."
282
371
  )
283
372
  await self._cleanup_existing_server()
284
373
  await asyncio.sleep(retry_delay)
285
374
  continue
375
+ # Specific error for port conflicts
286
376
  raise RuntimeError(
287
- f"scrcpy server failed after {max_retries} attempts: {error_msg}"
377
+ f"Port {self.port} persistently occupied after {max_retries} attempts. "
378
+ "Please check if another scrcpy instance is running."
288
379
  )
289
- raise RuntimeError(f"scrcpy server exited immediately: {error_msg}")
380
+ else:
381
+ # Non-port errors fail immediately (no retry)
382
+ logger.error(f"Scrcpy server startup failed: {error_msg[:200]}")
383
+ raise RuntimeError(f"Scrcpy server failed to start: {error_msg}")
290
384
 
385
+ logger.info("Scrcpy server started successfully")
291
386
  return
292
387
 
293
388
  raise RuntimeError("Failed to start scrcpy server after maximum retries")
294
389
 
295
390
  async def _connect_socket(self) -> None:
296
391
  """Connect to scrcpy TCP socket."""
297
- self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
298
- self.tcp_socket.settimeout(5)
392
+ # Retry connection with exponential backoff (max ~6 seconds total)
393
+ max_attempts = 10
394
+ retry_delay = 0.3
299
395
 
300
- try:
301
- self.tcp_socket.setsockopt(
302
- socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024
303
- )
304
- logger.debug("Set socket receive buffer to 2MB")
305
- except OSError as e:
306
- logger.warning(f"Failed to set socket buffer size: {e}")
396
+ for attempt in range(max_attempts):
397
+ # Create a fresh socket for each attempt to avoid "Invalid argument" error
398
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
399
+ sock.settimeout(5)
400
+
401
+ try:
402
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024)
403
+ except OSError as e:
404
+ logger.debug(f"Failed to set socket buffer size: {e}")
307
405
 
308
- for _ in range(5):
309
406
  try:
310
- self.tcp_socket.connect(("localhost", self.port))
311
- self.tcp_socket.settimeout(None)
407
+ sock.connect(("localhost", self.port))
408
+ sock.settimeout(None)
409
+ self.tcp_socket = sock # Only assign on success
410
+ logger.debug(f"Connected to scrcpy server on attempt {attempt + 1}")
312
411
  return
313
- except (ConnectionRefusedError, OSError):
314
- await asyncio.sleep(0.5)
412
+ except (ConnectionRefusedError, OSError) as e:
413
+ # Close the failed socket
414
+ try:
415
+ sock.close()
416
+ except Exception:
417
+ pass
418
+
419
+ if attempt < max_attempts - 1:
420
+ logger.debug(
421
+ f"Connection attempt {attempt + 1}/{max_attempts} failed: {e}. "
422
+ f"Retrying in {retry_delay}s..."
423
+ )
424
+ await asyncio.sleep(retry_delay)
425
+ # Gradually increase delay for later attempts
426
+ if attempt >= 3:
427
+ retry_delay = 0.5
428
+ else:
429
+ logger.error(
430
+ f"Failed to connect after {max_attempts} attempts. "
431
+ f"Last error: {e}"
432
+ )
315
433
 
316
434
  raise ConnectionError("Failed to connect to scrcpy server")
317
435
 
@@ -4,9 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import time
7
- from typing import NotRequired
8
7
 
9
- from typing_extensions import TypedDict
8
+ from typing_extensions import NotRequired, TypedDict
10
9
 
11
10
  import socketio
12
11
 
@@ -31,6 +30,9 @@ sio = socketio.AsyncServer(
31
30
 
32
31
  _socket_streamers: dict[str, ScrcpyStreamer] = {}
33
32
  _stream_tasks: dict[str, asyncio.Task] = {}
33
+ _device_locks: dict[
34
+ str, asyncio.Lock
35
+ ] = {} # Lock per device to prevent concurrent connections
34
36
 
35
37
 
36
38
  async def _stop_stream_for_sid(sid: str) -> None:
@@ -43,6 +45,46 @@ async def _stop_stream_for_sid(sid: str) -> None:
43
45
  streamer.stop()
44
46
 
45
47
 
48
+ def _classify_error(exc: Exception) -> dict:
49
+ """Classify error and return user-friendly message."""
50
+ error_str = str(exc)
51
+
52
+ if "Address already in use" in error_str or (
53
+ "Port" in error_str and "occupied" in error_str
54
+ ):
55
+ return {
56
+ "message": "端口冲突,视频流端口仍被占用。通常会自动解决,如果持续出现请重启应用。",
57
+ "type": "port_conflict",
58
+ "technical_details": error_str,
59
+ }
60
+ elif "Device" in error_str and (
61
+ "not available" in error_str or "not found" in error_str
62
+ ):
63
+ return {
64
+ "message": "设备无响应,请检查 USB/WiFi 连接。",
65
+ "type": "device_offline",
66
+ "technical_details": error_str,
67
+ }
68
+ elif "timeout" in error_str.lower() or "timed out" in error_str.lower():
69
+ return {
70
+ "message": "连接超时,请检查设备连接后重试。",
71
+ "type": "timeout",
72
+ "technical_details": error_str,
73
+ }
74
+ elif "Failed to connect" in error_str:
75
+ return {
76
+ "message": "无法连接到 scrcpy 服务器,请检查设备连接。",
77
+ "type": "connection_failed",
78
+ "technical_details": error_str,
79
+ }
80
+ else:
81
+ return {
82
+ "message": error_str,
83
+ "type": "unknown",
84
+ "technical_details": error_str,
85
+ }
86
+
87
+
46
88
  def stop_streamers(device_id: str | None = None) -> None:
47
89
  """Stop active scrcpy streamers (all or by device)."""
48
90
  sids = list(_socket_streamers.keys())
@@ -103,35 +145,66 @@ async def disconnect(sid: str) -> None:
103
145
  async def connect_device(sid: str, data: dict | None) -> None:
104
146
  payload = data or {}
105
147
  device_id = payload.get("device_id") or payload.get("deviceId")
148
+ if not device_id:
149
+ await sio.emit(
150
+ "error",
151
+ {"message": "Device ID is required", "type": "invalid_request"},
152
+ to=sid,
153
+ )
154
+ return
155
+
106
156
  max_size = int(payload.get("maxSize") or 1280)
107
157
  bit_rate = int(payload.get("bitRate") or 4_000_000)
108
158
 
159
+ # Stop any existing stream for this sid
109
160
  await _stop_stream_for_sid(sid)
110
161
 
111
- streamer = ScrcpyStreamer(
112
- device_id=device_id,
113
- max_size=max_size,
114
- bit_rate=bit_rate,
115
- )
116
-
117
- try:
118
- await streamer.start()
119
- metadata = await streamer.read_video_metadata()
120
- await sio.emit(
121
- "video-metadata",
122
- {
123
- "deviceName": metadata.device_name,
124
- "width": metadata.width,
125
- "height": metadata.height,
126
- "codec": metadata.codec,
127
- },
128
- to=sid,
162
+ # Get or create a lock for this device
163
+ if device_id not in _device_locks:
164
+ _device_locks[device_id] = asyncio.Lock()
165
+
166
+ device_lock = _device_locks[device_id]
167
+
168
+ # Acquire lock to prevent concurrent connections to the same device
169
+ async with device_lock:
170
+ logger.debug(f"Acquired device lock for {device_id}, sid: {sid}")
171
+
172
+ # Stop any existing streams for the same device (from other sids)
173
+ sids_to_stop = [
174
+ s
175
+ for s, streamer in _socket_streamers.items()
176
+ if s != sid and streamer.device_id == device_id
177
+ ]
178
+ for s in sids_to_stop:
179
+ logger.info(f"Stopping existing stream for device {device_id} from sid {s}")
180
+ await _stop_stream_for_sid(s)
181
+
182
+ streamer = ScrcpyStreamer(
183
+ device_id=device_id,
184
+ max_size=max_size,
185
+ bit_rate=bit_rate,
129
186
  )
130
- except Exception as exc:
131
- streamer.stop()
132
- logger.exception("Failed to start scrcpy stream: %s", exc)
133
- await sio.emit("error", {"message": str(exc)}, to=sid)
134
- return
135
187
 
136
- _socket_streamers[sid] = streamer
137
- _stream_tasks[sid] = asyncio.create_task(_stream_packets(sid, streamer))
188
+ try:
189
+ await streamer.start() # ScrcpyStreamer has built-in retry logic
190
+ metadata = await streamer.read_video_metadata()
191
+ await sio.emit(
192
+ "video-metadata",
193
+ {
194
+ "deviceName": metadata.device_name,
195
+ "width": metadata.width,
196
+ "height": metadata.height,
197
+ "codec": metadata.codec,
198
+ },
199
+ to=sid,
200
+ )
201
+
202
+ _socket_streamers[sid] = streamer
203
+ _stream_tasks[sid] = asyncio.create_task(_stream_packets(sid, streamer))
204
+
205
+ except Exception as exc:
206
+ streamer.stop()
207
+ logger.exception("Failed to start scrcpy stream: %s", exc)
208
+ # Use unified error classification
209
+ error_info = _classify_error(exc)
210
+ await sio.emit("error", error_info, to=sid)
@@ -1 +1 @@
1
- import{j as o}from"./index-UYYauTly.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};
1
+ import{j as o}from"./index-CssG-3TH.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};
@@ -0,0 +1 @@
1
+ import{o as u,r as o,j as a,b as r,B as d}from"./index-CssG-3TH.js";import{P as g,b as x,c as f,d as m}from"./popover-DLsuV5Sx.js";import{D as p,d as h,e as w,f as j,g as D}from"./dialog-DZ78cEcj.js";const N=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],b=u("check",N),c=o.createContext(void 0),P=({value:t="",onValueChange:e,children:s})=>{const[n,l]=o.useState(!1);return a.jsx(c.Provider,{value:{value:t,onValueChange:e||(()=>{}),open:n,setOpen:l},children:a.jsx(g,{open:n,onOpenChange:l,children:s})})},C=o.forwardRef(({className:t,children:e,...s},n)=>{if(!o.useContext(c))throw new Error("SelectTrigger must be used within Select");return a.jsx(x,{asChild:!0,children:a.jsxs("button",{ref:n,className:r("flex h-10 w-full items-center justify-between rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus:ring-slate-300",t),...s,children:[e,a.jsx(f,{className:"h-4 w-4 opacity-50"})]})})});C.displayName="SelectTrigger";const V=({placeholder:t})=>{const e=o.useContext(c);if(!e)throw new Error("SelectValue must be used within Select");return a.jsx("span",{className:e.value?"":"text-slate-500",children:e.value||t})},I=({children:t,className:e})=>a.jsx(m,{className:r("w-[var(--radix-popover-trigger-width)] p-1",e),children:t}),O=({value:t,children:e,disabled:s,className:n})=>{const l=o.useContext(c);if(!l)throw new Error("SelectItem must be used within Select");const i=l.value===t;return a.jsxs("div",{role:"option","aria-selected":i,className:r("relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-slate-100 focus:bg-slate-100 dark:hover:bg-slate-800 dark:focus:bg-slate-800",s&&"pointer-events-none opacity-50",n),onClick:()=>{s||(l.onValueChange(t),l.setOpen(!1))},children:[a.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:i&&a.jsx(b,{className:"h-4 w-4"})}),e]})},B=({open:t,onOpenChange:e,children:s})=>a.jsx(p,{open:t,onOpenChange:e,children:s}),v=o.forwardRef(({className:t,...e},s)=>a.jsx(h,{ref:s,className:r("sm:max-w-[425px]",t),...e}));v.displayName="AlertDialogContent";const F=({className:t,...e})=>a.jsx(w,{className:r(t),...e}),H=({className:t,...e})=>a.jsx(D,{className:r(t),...e}),A=o.forwardRef(({className:t,...e},s)=>a.jsx(j,{ref:s,className:r(t),...e}));A.displayName="AlertDialogTitle";const S=o.forwardRef(({className:t,...e},s)=>a.jsx("p",{ref:s,className:r("text-sm text-slate-500 dark:text-slate-400",t),...e}));S.displayName="AlertDialogDescription";const y=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,className:r(t),...e}));y.displayName="AlertDialogAction";const k=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,variant:"outline",className:r(t),...e}));k.displayName="AlertDialogCancel";export{B as A,P as S,C as a,V as b,I as c,O as d,v as e,F as f,A as g,S as h,H as i,k as j,y as k};