hud-python 0.4.1__py3-none-any.whl → 0.4.3__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.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/utils/progress.py
CHANGED
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import time
|
|
4
|
-
from collections import defaultdict
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class StepProgressTracker:
|
|
8
|
-
"""
|
|
9
|
-
Tracks progress across potentially parallel async tasks based on steps completed.
|
|
10
|
-
Provides estimates assuming tasks run up to max_steps_per_task.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, total_tasks: int, max_steps_per_task: int) -> None:
|
|
14
|
-
"""
|
|
15
|
-
Initialize the StepProgressTracker.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
total_tasks: The total number of tasks to track.
|
|
19
|
-
max_steps_per_task: The maximum number of steps per task.
|
|
20
|
-
|
|
21
|
-
Raises:
|
|
22
|
-
ValueError: If total_tasks or max_steps_per_task is not positive.
|
|
23
|
-
"""
|
|
24
|
-
if total_tasks <= 0:
|
|
25
|
-
raise ValueError("total_tasks must be positive")
|
|
26
|
-
if max_steps_per_task <= 0:
|
|
27
|
-
raise ValueError("max_steps_per_task must be positive")
|
|
28
|
-
|
|
29
|
-
self.total_tasks = total_tasks
|
|
30
|
-
self.max_steps_per_task = max_steps_per_task
|
|
31
|
-
self.total_potential_steps = total_tasks * max_steps_per_task
|
|
32
|
-
|
|
33
|
-
# Use asyncio.Lock for potentially concurrent updates/reads if needed,
|
|
34
|
-
# but start without for simplicity in single-threaded asyncio.
|
|
35
|
-
# self._lock = asyncio.Lock()
|
|
36
|
-
self._task_steps: dict[str, int] = defaultdict(int)
|
|
37
|
-
self._finished_tasks: dict[str, bool] = defaultdict(bool)
|
|
38
|
-
self._tasks_started = 0
|
|
39
|
-
self._tasks_finished = 0
|
|
40
|
-
|
|
41
|
-
self.start_time: float | None = None
|
|
42
|
-
self.current_total_steps = 0
|
|
43
|
-
|
|
44
|
-
def start_task(self, task_id: str) -> None:
|
|
45
|
-
# async with self._lock: # If using lock
|
|
46
|
-
if self.start_time is None:
|
|
47
|
-
self.start_time = time.monotonic()
|
|
48
|
-
self._task_steps[task_id] = 0
|
|
49
|
-
self._finished_tasks[task_id] = False
|
|
50
|
-
self._tasks_started += 1
|
|
51
|
-
|
|
52
|
-
def increment_step(self, task_id: str) -> None:
|
|
53
|
-
# async with self._lock:
|
|
54
|
-
if (
|
|
55
|
-
not self._finished_tasks[task_id]
|
|
56
|
-
and self._task_steps[task_id] < self.max_steps_per_task
|
|
57
|
-
):
|
|
58
|
-
self._task_steps[task_id] += 1
|
|
59
|
-
# Update overall progress immediately
|
|
60
|
-
self._update_total_steps()
|
|
61
|
-
|
|
62
|
-
def finish_task(self, task_id: str) -> None:
|
|
63
|
-
# async with self._lock:
|
|
64
|
-
if not self._finished_tasks[task_id]:
|
|
65
|
-
# For calculation, consider a finished task as having completed max steps
|
|
66
|
-
self._task_steps[task_id] = self.max_steps_per_task
|
|
67
|
-
self._finished_tasks[task_id] = True
|
|
68
|
-
self._tasks_finished += 1
|
|
69
|
-
# Update overall progress
|
|
70
|
-
self._update_total_steps()
|
|
71
|
-
|
|
72
|
-
def _update_total_steps(self) -> None:
|
|
73
|
-
# This could be expensive if called extremely frequently.
|
|
74
|
-
# Called after increment or finish.
|
|
75
|
-
# async with self._lock:
|
|
76
|
-
self.current_total_steps = sum(self._task_steps.values())
|
|
77
|
-
|
|
78
|
-
def get_progress(self) -> tuple[int, int, float]:
|
|
79
|
-
"""Returns (current_steps, total_potential_steps, percentage)."""
|
|
80
|
-
# async with self._lock:
|
|
81
|
-
# Recalculate here for safety, though _update_total_steps should keep it current
|
|
82
|
-
# current_steps = sum(self._task_steps.values())
|
|
83
|
-
current_steps = self.current_total_steps
|
|
84
|
-
|
|
85
|
-
percentage = 0.0
|
|
86
|
-
if self.total_potential_steps > 0:
|
|
87
|
-
percentage = (current_steps / self.total_potential_steps) * 100
|
|
88
|
-
return current_steps, self.total_potential_steps, percentage
|
|
89
|
-
|
|
90
|
-
def get_stats(self) -> tuple[float, float | None]:
|
|
91
|
-
"""Returns (rate_steps_per_minute, eta_seconds_upper_bound)."""
|
|
92
|
-
# async with self._lock:
|
|
93
|
-
if self.start_time is None or self._tasks_started == 0:
|
|
94
|
-
return 0.0, None # No rate or ETA yet
|
|
95
|
-
|
|
96
|
-
elapsed_time = time.monotonic() - self.start_time
|
|
97
|
-
current_steps = self.current_total_steps
|
|
98
|
-
|
|
99
|
-
rate_sec = 0.0
|
|
100
|
-
if elapsed_time > 0:
|
|
101
|
-
rate_sec = current_steps / elapsed_time
|
|
102
|
-
|
|
103
|
-
rate_min = rate_sec * 60 # Convert rate to steps per minute
|
|
104
|
-
|
|
105
|
-
eta = None
|
|
106
|
-
# ETA calculation still uses rate_sec (steps/second) for time estimation in seconds
|
|
107
|
-
if rate_sec > 0:
|
|
108
|
-
remaining_steps = self.total_potential_steps - current_steps
|
|
109
|
-
eta = remaining_steps / rate_sec if remaining_steps > 0 else 0.0
|
|
110
|
-
|
|
111
|
-
return rate_min, eta # Return rate in steps/min
|
|
112
|
-
|
|
113
|
-
def is_finished(self) -> bool:
|
|
114
|
-
# async with self._lock:
|
|
115
|
-
return self._tasks_finished >= self.total_tasks
|
|
116
|
-
|
|
117
|
-
def display(self, bar_length: int = 40) -> str:
|
|
118
|
-
"""Generates a progress string similar to tqdm."""
|
|
119
|
-
current_steps, total_steps, percentage = self.get_progress()
|
|
120
|
-
rate_min, eta = self.get_stats() # Rate is now per minute
|
|
121
|
-
|
|
122
|
-
# Ensure valid values for display
|
|
123
|
-
current_steps = min(current_steps, total_steps)
|
|
124
|
-
percentage = max(0.0, min(100.0, percentage))
|
|
125
|
-
|
|
126
|
-
filled_length = int(bar_length * current_steps // total_steps) if total_steps else 0
|
|
127
|
-
bar = "█" * filled_length + "-" * (bar_length - filled_length)
|
|
128
|
-
|
|
129
|
-
# Format time
|
|
130
|
-
elapsed_str = "0:00"
|
|
131
|
-
eta_str = "??:??"
|
|
132
|
-
if self.start_time:
|
|
133
|
-
elapsed_seconds = int(time.monotonic() - self.start_time)
|
|
134
|
-
elapsed_str = f"{elapsed_seconds // 60}:{elapsed_seconds % 60:02d}"
|
|
135
|
-
if eta is not None:
|
|
136
|
-
eta_seconds = int(eta)
|
|
137
|
-
eta_str = f"{eta_seconds // 60}:{eta_seconds % 60:02d}"
|
|
138
|
-
elif self.is_finished():
|
|
139
|
-
eta_str = "0:00"
|
|
140
|
-
|
|
141
|
-
# Update rate string format
|
|
142
|
-
rate_str = f"{rate_min:.1f} steps/min" if rate_min > 0 else "?? steps/min"
|
|
143
|
-
|
|
144
|
-
# Format steps - use K/M for large numbers if desired, keep simple for now
|
|
145
|
-
steps_str = f"{current_steps}/{total_steps}"
|
|
146
|
-
|
|
147
|
-
# tasks_str = f" {self._tasks_finished}/{self.total_tasks} tasks" # Optional tasks counter
|
|
148
|
-
|
|
149
|
-
return f"{percentage:3.0f}%|{bar}| {steps_str} [{elapsed_str}<{eta_str}, {rate_str}]"
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StepProgressTracker:
|
|
8
|
+
"""
|
|
9
|
+
Tracks progress across potentially parallel async tasks based on steps completed.
|
|
10
|
+
Provides estimates assuming tasks run up to max_steps_per_task.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, total_tasks: int, max_steps_per_task: int) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Initialize the StepProgressTracker.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
total_tasks: The total number of tasks to track.
|
|
19
|
+
max_steps_per_task: The maximum number of steps per task.
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ValueError: If total_tasks or max_steps_per_task is not positive.
|
|
23
|
+
"""
|
|
24
|
+
if total_tasks <= 0:
|
|
25
|
+
raise ValueError("total_tasks must be positive")
|
|
26
|
+
if max_steps_per_task <= 0:
|
|
27
|
+
raise ValueError("max_steps_per_task must be positive")
|
|
28
|
+
|
|
29
|
+
self.total_tasks = total_tasks
|
|
30
|
+
self.max_steps_per_task = max_steps_per_task
|
|
31
|
+
self.total_potential_steps = total_tasks * max_steps_per_task
|
|
32
|
+
|
|
33
|
+
# Use asyncio.Lock for potentially concurrent updates/reads if needed,
|
|
34
|
+
# but start without for simplicity in single-threaded asyncio.
|
|
35
|
+
# self._lock = asyncio.Lock()
|
|
36
|
+
self._task_steps: dict[str, int] = defaultdict(int)
|
|
37
|
+
self._finished_tasks: dict[str, bool] = defaultdict(bool)
|
|
38
|
+
self._tasks_started = 0
|
|
39
|
+
self._tasks_finished = 0
|
|
40
|
+
|
|
41
|
+
self.start_time: float | None = None
|
|
42
|
+
self.current_total_steps = 0
|
|
43
|
+
|
|
44
|
+
def start_task(self, task_id: str) -> None:
|
|
45
|
+
# async with self._lock: # If using lock
|
|
46
|
+
if self.start_time is None:
|
|
47
|
+
self.start_time = time.monotonic()
|
|
48
|
+
self._task_steps[task_id] = 0
|
|
49
|
+
self._finished_tasks[task_id] = False
|
|
50
|
+
self._tasks_started += 1
|
|
51
|
+
|
|
52
|
+
def increment_step(self, task_id: str) -> None:
|
|
53
|
+
# async with self._lock:
|
|
54
|
+
if (
|
|
55
|
+
not self._finished_tasks[task_id]
|
|
56
|
+
and self._task_steps[task_id] < self.max_steps_per_task
|
|
57
|
+
):
|
|
58
|
+
self._task_steps[task_id] += 1
|
|
59
|
+
# Update overall progress immediately
|
|
60
|
+
self._update_total_steps()
|
|
61
|
+
|
|
62
|
+
def finish_task(self, task_id: str) -> None:
|
|
63
|
+
# async with self._lock:
|
|
64
|
+
if not self._finished_tasks[task_id]:
|
|
65
|
+
# For calculation, consider a finished task as having completed max steps
|
|
66
|
+
self._task_steps[task_id] = self.max_steps_per_task
|
|
67
|
+
self._finished_tasks[task_id] = True
|
|
68
|
+
self._tasks_finished += 1
|
|
69
|
+
# Update overall progress
|
|
70
|
+
self._update_total_steps()
|
|
71
|
+
|
|
72
|
+
def _update_total_steps(self) -> None:
|
|
73
|
+
# This could be expensive if called extremely frequently.
|
|
74
|
+
# Called after increment or finish.
|
|
75
|
+
# async with self._lock:
|
|
76
|
+
self.current_total_steps = sum(self._task_steps.values())
|
|
77
|
+
|
|
78
|
+
def get_progress(self) -> tuple[int, int, float]:
|
|
79
|
+
"""Returns (current_steps, total_potential_steps, percentage)."""
|
|
80
|
+
# async with self._lock:
|
|
81
|
+
# Recalculate here for safety, though _update_total_steps should keep it current
|
|
82
|
+
# current_steps = sum(self._task_steps.values())
|
|
83
|
+
current_steps = self.current_total_steps
|
|
84
|
+
|
|
85
|
+
percentage = 0.0
|
|
86
|
+
if self.total_potential_steps > 0:
|
|
87
|
+
percentage = (current_steps / self.total_potential_steps) * 100
|
|
88
|
+
return current_steps, self.total_potential_steps, percentage
|
|
89
|
+
|
|
90
|
+
def get_stats(self) -> tuple[float, float | None]:
|
|
91
|
+
"""Returns (rate_steps_per_minute, eta_seconds_upper_bound)."""
|
|
92
|
+
# async with self._lock:
|
|
93
|
+
if self.start_time is None or self._tasks_started == 0:
|
|
94
|
+
return 0.0, None # No rate or ETA yet
|
|
95
|
+
|
|
96
|
+
elapsed_time = time.monotonic() - self.start_time
|
|
97
|
+
current_steps = self.current_total_steps
|
|
98
|
+
|
|
99
|
+
rate_sec = 0.0
|
|
100
|
+
if elapsed_time > 0:
|
|
101
|
+
rate_sec = current_steps / elapsed_time
|
|
102
|
+
|
|
103
|
+
rate_min = rate_sec * 60 # Convert rate to steps per minute
|
|
104
|
+
|
|
105
|
+
eta = None
|
|
106
|
+
# ETA calculation still uses rate_sec (steps/second) for time estimation in seconds
|
|
107
|
+
if rate_sec > 0:
|
|
108
|
+
remaining_steps = self.total_potential_steps - current_steps
|
|
109
|
+
eta = remaining_steps / rate_sec if remaining_steps > 0 else 0.0
|
|
110
|
+
|
|
111
|
+
return rate_min, eta # Return rate in steps/min
|
|
112
|
+
|
|
113
|
+
def is_finished(self) -> bool:
|
|
114
|
+
# async with self._lock:
|
|
115
|
+
return self._tasks_finished >= self.total_tasks
|
|
116
|
+
|
|
117
|
+
def display(self, bar_length: int = 40) -> str:
|
|
118
|
+
"""Generates a progress string similar to tqdm."""
|
|
119
|
+
current_steps, total_steps, percentage = self.get_progress()
|
|
120
|
+
rate_min, eta = self.get_stats() # Rate is now per minute
|
|
121
|
+
|
|
122
|
+
# Ensure valid values for display
|
|
123
|
+
current_steps = min(current_steps, total_steps)
|
|
124
|
+
percentage = max(0.0, min(100.0, percentage))
|
|
125
|
+
|
|
126
|
+
filled_length = int(bar_length * current_steps // total_steps) if total_steps else 0
|
|
127
|
+
bar = "█" * filled_length + "-" * (bar_length - filled_length)
|
|
128
|
+
|
|
129
|
+
# Format time
|
|
130
|
+
elapsed_str = "0:00"
|
|
131
|
+
eta_str = "??:??"
|
|
132
|
+
if self.start_time:
|
|
133
|
+
elapsed_seconds = int(time.monotonic() - self.start_time)
|
|
134
|
+
elapsed_str = f"{elapsed_seconds // 60}:{elapsed_seconds % 60:02d}"
|
|
135
|
+
if eta is not None:
|
|
136
|
+
eta_seconds = int(eta)
|
|
137
|
+
eta_str = f"{eta_seconds // 60}:{eta_seconds % 60:02d}"
|
|
138
|
+
elif self.is_finished():
|
|
139
|
+
eta_str = "0:00"
|
|
140
|
+
|
|
141
|
+
# Update rate string format
|
|
142
|
+
rate_str = f"{rate_min:.1f} steps/min" if rate_min > 0 else "?? steps/min"
|
|
143
|
+
|
|
144
|
+
# Format steps - use K/M for large numbers if desired, keep simple for now
|
|
145
|
+
steps_str = f"{current_steps}/{total_steps}"
|
|
146
|
+
|
|
147
|
+
# tasks_str = f" {self._tasks_finished}/{self.total_tasks} tasks" # Optional tasks counter
|
|
148
|
+
|
|
149
|
+
return f"{percentage:3.0f}%|{bar}| {steps_str} [{elapsed_str}<{eta_str}, {rate_str}]"
|
hud/utils/telemetry.py
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
|
-
logger = logging.getLogger(__name__)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def stream(live_url: str) -> str:
|
|
9
|
-
"""
|
|
10
|
-
Display a stream in the HUD system.
|
|
11
|
-
"""
|
|
12
|
-
from IPython.display import HTML, display
|
|
13
|
-
|
|
14
|
-
html_content = f"""
|
|
15
|
-
<div style="width: 960px; height: 540px; overflow: hidden;">
|
|
16
|
-
<div style="transform: scale(0.5); transform-origin: top left;">
|
|
17
|
-
<iframe src="{live_url}" width="1920" height="1080" style="border: 1px solid #ddd;">
|
|
18
|
-
</iframe>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
"""
|
|
22
|
-
try:
|
|
23
|
-
display(HTML(html_content))
|
|
24
|
-
except Exception as e:
|
|
25
|
-
logger.warning(e)
|
|
26
|
-
|
|
27
|
-
return html_content
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def display_screenshot(base64_image: str, width: int = 960, height: int = 540) -> str:
|
|
31
|
-
"""
|
|
32
|
-
Display a base64-encoded screenshot image.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
base64_image: Base64-encoded image string (without the data URI prefix)
|
|
36
|
-
width: Display width in pixels
|
|
37
|
-
height: Display height in pixels
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
The HTML string used to display the image
|
|
41
|
-
|
|
42
|
-
Note:
|
|
43
|
-
This function will both display the image in IPython environments
|
|
44
|
-
and return the HTML string for other contexts.
|
|
45
|
-
"""
|
|
46
|
-
from IPython.display import HTML, display
|
|
47
|
-
|
|
48
|
-
# Ensure the base64 image doesn't already have the data URI prefix
|
|
49
|
-
if base64_image.startswith("data:image"):
|
|
50
|
-
img_src = base64_image
|
|
51
|
-
else:
|
|
52
|
-
img_src = f"data:image/png;base64,{base64_image}"
|
|
53
|
-
|
|
54
|
-
html_content = f"""
|
|
55
|
-
<div style="width: {width}px; height: {height}px; overflow: hidden; margin: 10px 0; border: 1px solid #ddd;">
|
|
56
|
-
<img src="{img_src}" style="max-width: 100%; max-height: 100%;">
|
|
57
|
-
</div>
|
|
58
|
-
""" # noqa: E501
|
|
59
|
-
|
|
60
|
-
# Display in IPython environments
|
|
61
|
-
try:
|
|
62
|
-
display(HTML(html_content))
|
|
63
|
-
except Exception as e:
|
|
64
|
-
logger.warning(e)
|
|
65
|
-
|
|
66
|
-
return html_content
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def stream(live_url: str) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Display a stream in the HUD system.
|
|
11
|
+
"""
|
|
12
|
+
from IPython.display import HTML, display
|
|
13
|
+
|
|
14
|
+
html_content = f"""
|
|
15
|
+
<div style="width: 960px; height: 540px; overflow: hidden;">
|
|
16
|
+
<div style="transform: scale(0.5); transform-origin: top left;">
|
|
17
|
+
<iframe src="{live_url}" width="1920" height="1080" style="border: 1px solid #ddd;">
|
|
18
|
+
</iframe>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
display(HTML(html_content))
|
|
24
|
+
except Exception as e:
|
|
25
|
+
logger.warning(e)
|
|
26
|
+
|
|
27
|
+
return html_content
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def display_screenshot(base64_image: str, width: int = 960, height: int = 540) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Display a base64-encoded screenshot image.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
base64_image: Base64-encoded image string (without the data URI prefix)
|
|
36
|
+
width: Display width in pixels
|
|
37
|
+
height: Display height in pixels
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The HTML string used to display the image
|
|
41
|
+
|
|
42
|
+
Note:
|
|
43
|
+
This function will both display the image in IPython environments
|
|
44
|
+
and return the HTML string for other contexts.
|
|
45
|
+
"""
|
|
46
|
+
from IPython.display import HTML, display
|
|
47
|
+
|
|
48
|
+
# Ensure the base64 image doesn't already have the data URI prefix
|
|
49
|
+
if base64_image.startswith("data:image"):
|
|
50
|
+
img_src = base64_image
|
|
51
|
+
else:
|
|
52
|
+
img_src = f"data:image/png;base64,{base64_image}"
|
|
53
|
+
|
|
54
|
+
html_content = f"""
|
|
55
|
+
<div style="width: {width}px; height: {height}px; overflow: hidden; margin: 10px 0; border: 1px solid #ddd;">
|
|
56
|
+
<img src="{img_src}" style="max-width: 100%; max-height: 100%;">
|
|
57
|
+
</div>
|
|
58
|
+
""" # noqa: E501
|
|
59
|
+
|
|
60
|
+
# Display in IPython environments
|
|
61
|
+
try:
|
|
62
|
+
display(HTML(html_content))
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.warning(e)
|
|
65
|
+
|
|
66
|
+
return html_content
|