feat: checkpoint device agent runtime milestones

This commit is contained in:
2026-07-06 17:24:03 +08:00
parent 2d4251e98e
commit 5658735bca
153 changed files with 8060 additions and 65 deletions
+91
View File
@@ -0,0 +1,91 @@
from __future__ import annotations
from dataclasses import replace
from pathlib import Path
from struct import unpack
from core.models import Bounds, Scene, SceneElement
def build_scene(
*,
screen_width: int,
screen_height: int,
ui_elements: list[SceneElement] | None = None,
ocr_elements: list[SceneElement] | None = None,
iou_threshold: float = 0.5,
) -> Scene:
ui_elements = ui_elements or []
ocr_elements = ocr_elements or []
merged: list[SceneElement] = []
used_ocr: set[int] = set()
for ui_index, ui_element in enumerate(ui_elements):
best_index: int | None = None
best_iou = 0.0
for ocr_index, ocr_element in enumerate(ocr_elements):
if ocr_index in used_ocr:
continue
score = bbox_iou(ui_element.bounds, ocr_element.bounds)
if score > best_iou:
best_iou = score
best_index = ocr_index
element = _with_id(ui_element, f"ui-{ui_index:03d}")
if best_index is not None and best_iou >= iou_threshold:
used_ocr.add(best_index)
ocr_element = ocr_elements[best_index]
element = replace(
element,
text=element.text or ocr_element.text,
confidence=_best_confidence(element.confidence, ocr_element.confidence),
)
merged.append(element)
for ocr_index, ocr_element in enumerate(ocr_elements):
if ocr_index in used_ocr:
continue
merged.append(_with_id(ocr_element, f"ocr-{ocr_index:03d}"))
return Scene(width=screen_width, height=screen_height, elements=merged)
def bbox_iou(first: Bounds, second: Bounds) -> float:
x_left = max(first.x, second.x)
y_top = max(first.y, second.y)
x_right = min(first.right, second.right)
y_bottom = min(first.bottom, second.bottom)
if x_right <= x_left or y_bottom <= y_top:
return 0.0
intersection = (x_right - x_left) * (y_bottom - y_top)
first_area = first.width * first.height
second_area = second.width * second.height
union = first_area + second_area - intersection
if union <= 0:
return 0.0
return intersection / union
def infer_png_size(image: bytes | str | Path | None) -> tuple[int, int]:
if image is None:
return (0, 0)
data = Path(image).read_bytes() if not isinstance(image, bytes) else image
if len(data) >= 24 and data[:8] == b"\x89PNG\r\n\x1a\n":
width, height = unpack(">II", data[16:24])
return (int(width), int(height))
return (0, 0)
def _with_id(element: SceneElement, fallback_id: str) -> SceneElement:
if element.id:
return element
return replace(element, id=fallback_id)
def _best_confidence(first: float | None, second: float | None) -> float | None:
values = [value for value in (first, second) if value is not None]
if not values:
return None
return max(values)