fix keypoint selection image size!!!!!!
Browse files
gradio-web/test/conftest.py
CHANGED
|
@@ -18,7 +18,9 @@ def _find_repo_root(start_file: str | Path) -> Path:
|
|
| 18 |
|
| 19 |
REPO_ROOT = _find_repo_root(__file__)
|
| 20 |
SRC_ROOT = REPO_ROOT / "src"
|
| 21 |
-
GRADIO_ROOT = REPO_ROOT / "gradio"
|
|
|
|
|
|
|
| 22 |
|
| 23 |
for p in (str(REPO_ROOT), str(SRC_ROOT), str(GRADIO_ROOT)):
|
| 24 |
if p not in sys.path:
|
|
|
|
| 18 |
|
| 19 |
REPO_ROOT = _find_repo_root(__file__)
|
| 20 |
SRC_ROOT = REPO_ROOT / "src"
|
| 21 |
+
GRADIO_ROOT = REPO_ROOT / "gradio-web"
|
| 22 |
+
if not GRADIO_ROOT.exists():
|
| 23 |
+
GRADIO_ROOT = REPO_ROOT / "gradio"
|
| 24 |
|
| 25 |
for p in (str(REPO_ROOT), str(SRC_ROOT), str(GRADIO_ROOT)):
|
| 26 |
if p not in sys.path:
|
gradio-web/test/test_ui_phase_machine_runtime_e2e.py
CHANGED
|
@@ -61,6 +61,43 @@ def _read_header_task_value(page) -> str | None:
|
|
| 61 |
)
|
| 62 |
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
@pytest.fixture
|
| 65 |
def phase_machine_ui_url():
|
| 66 |
state = {"precheck_calls": 0}
|
|
@@ -455,6 +492,245 @@ def test_unified_loading_overlay_init_flow(monkeypatch):
|
|
| 455 |
assert calls["init"] >= 1
|
| 456 |
|
| 457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
def test_header_task_shows_env_after_init(monkeypatch):
|
| 459 |
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
| 460 |
|
|
|
|
| 61 |
)
|
| 62 |
|
| 63 |
|
| 64 |
+
def _read_coords_box_value(page) -> str | None:
|
| 65 |
+
return page.evaluate(
|
| 66 |
+
"""() => {
|
| 67 |
+
const root = document.getElementById('coords_box');
|
| 68 |
+
if (!root) return null;
|
| 69 |
+
const field = root.querySelector('textarea, input');
|
| 70 |
+
if (!field) return null;
|
| 71 |
+
const value = typeof field.value === 'string' ? field.value.trim() : '';
|
| 72 |
+
return value || null;
|
| 73 |
+
}"""
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _read_live_obs_geometry(page) -> dict[str, dict[str, float] | None]:
|
| 78 |
+
return page.evaluate(
|
| 79 |
+
"""() => {
|
| 80 |
+
const root = document.getElementById('live_obs');
|
| 81 |
+
const container = root?.querySelector('.image-container');
|
| 82 |
+
const uploadContainer = root?.querySelector('.upload-container');
|
| 83 |
+
const frame = root?.querySelector('.image-frame');
|
| 84 |
+
const img = root?.querySelector('img');
|
| 85 |
+
const measure = (node) => {
|
| 86 |
+
if (!node) return null;
|
| 87 |
+
const rect = node.getBoundingClientRect();
|
| 88 |
+
return { width: rect.width, height: rect.height };
|
| 89 |
+
};
|
| 90 |
+
return {
|
| 91 |
+
root: measure(root),
|
| 92 |
+
container: measure(container),
|
| 93 |
+
uploadContainer: measure(uploadContainer),
|
| 94 |
+
frame: measure(frame),
|
| 95 |
+
img: measure(img),
|
| 96 |
+
};
|
| 97 |
+
}"""
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
@pytest.fixture
|
| 102 |
def phase_machine_ui_url():
|
| 103 |
state = {"precheck_calls": 0}
|
|
|
|
| 492 |
assert calls["init"] >= 1
|
| 493 |
|
| 494 |
|
| 495 |
+
def test_live_obs_client_resize_fills_width_and_keeps_click_mapping(monkeypatch):
|
| 496 |
+
callbacks = importlib.reload(importlib.import_module("gradio_callbacks"))
|
| 497 |
+
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
| 498 |
+
|
| 499 |
+
fake_obs = np.zeros((24, 48, 3), dtype=np.uint8)
|
| 500 |
+
fake_obs_img = Image.fromarray(fake_obs)
|
| 501 |
+
|
| 502 |
+
class FakeSession:
|
| 503 |
+
raw_solve_options = [{"available": True}]
|
| 504 |
+
|
| 505 |
+
def get_pil_image(self, use_segmented=False):
|
| 506 |
+
_ = use_segmented
|
| 507 |
+
return fake_obs_img.copy()
|
| 508 |
+
|
| 509 |
+
def fake_init_app(_request=None):
|
| 510 |
+
return (
|
| 511 |
+
"uid-live-obs-resize",
|
| 512 |
+
gr.update(visible=True), # main_interface
|
| 513 |
+
gr.update(value=fake_obs_img.copy(), interactive=False), # img_display
|
| 514 |
+
"ready", # log_output
|
| 515 |
+
gr.update(choices=[("pick", 0)], value=0), # options_radio
|
| 516 |
+
"goal", # goal_box
|
| 517 |
+
gr.update(
|
| 518 |
+
value="please click the keypoint selection image",
|
| 519 |
+
visible=True,
|
| 520 |
+
interactive=False,
|
| 521 |
+
), # coords_box
|
| 522 |
+
gr.update(value=None, visible=False), # video_display
|
| 523 |
+
"ResizeEnv (Episode 1)", # task_info_box
|
| 524 |
+
"Completed: 0", # progress_info_box
|
| 525 |
+
gr.update(interactive=True), # restart_episode_btn
|
| 526 |
+
gr.update(interactive=True), # next_task_btn
|
| 527 |
+
gr.update(interactive=True), # exec_btn
|
| 528 |
+
gr.update(visible=False), # video_phase_group
|
| 529 |
+
gr.update(visible=True), # action_phase_group
|
| 530 |
+
gr.update(visible=True), # control_panel_group
|
| 531 |
+
gr.update(value="hint"), # task_hint_display
|
| 532 |
+
gr.update(visible=False), # loading_overlay
|
| 533 |
+
gr.update(interactive=True), # reference_action_btn
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
monkeypatch.setattr(ui_layout, "init_app", fake_init_app)
|
| 537 |
+
monkeypatch.setattr(callbacks, "get_session", lambda uid: FakeSession())
|
| 538 |
+
monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
|
| 539 |
+
|
| 540 |
+
demo = ui_layout.create_ui_blocks()
|
| 541 |
+
|
| 542 |
+
port = _free_port()
|
| 543 |
+
host = "127.0.0.1"
|
| 544 |
+
root_url = f"http://{host}:{port}/"
|
| 545 |
+
|
| 546 |
+
app = FastAPI(title="live-obs-client-resize-test")
|
| 547 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
| 548 |
+
|
| 549 |
+
config = uvicorn.Config(app, host=host, port=port, log_level="error")
|
| 550 |
+
server = uvicorn.Server(config)
|
| 551 |
+
thread = threading.Thread(target=server.run, daemon=True)
|
| 552 |
+
thread.start()
|
| 553 |
+
_wait_http_ready(root_url)
|
| 554 |
+
|
| 555 |
+
try:
|
| 556 |
+
with sync_playwright() as p:
|
| 557 |
+
browser = p.chromium.launch(headless=True)
|
| 558 |
+
page = browser.new_page(viewport={"width": 1280, "height": 900})
|
| 559 |
+
page.goto(root_url, wait_until="domcontentloaded")
|
| 560 |
+
page.wait_for_selector("#main_interface_root", state="visible", timeout=15000)
|
| 561 |
+
page.wait_for_selector("#live_obs img", timeout=15000)
|
| 562 |
+
page.wait_for_selector("#coords_box textarea, #coords_box input", timeout=15000)
|
| 563 |
+
page.wait_for_function(
|
| 564 |
+
"""() => {
|
| 565 |
+
const container = document.querySelector('#live_obs .image-container');
|
| 566 |
+
const img = document.querySelector('#live_obs img');
|
| 567 |
+
if (!container || !img) return false;
|
| 568 |
+
const containerRect = container.getBoundingClientRect();
|
| 569 |
+
const imgRect = img.getBoundingClientRect();
|
| 570 |
+
return imgRect.width > 200 && Math.abs(containerRect.width - imgRect.width) <= 2;
|
| 571 |
+
}""",
|
| 572 |
+
timeout=10000,
|
| 573 |
+
)
|
| 574 |
+
|
| 575 |
+
initial_geometry = _read_live_obs_geometry(page)
|
| 576 |
+
assert initial_geometry["container"] is not None
|
| 577 |
+
assert initial_geometry["img"] is not None
|
| 578 |
+
assert initial_geometry["uploadContainer"] is not None
|
| 579 |
+
assert initial_geometry["frame"] is not None
|
| 580 |
+
assert initial_geometry["img"]["width"] > 200
|
| 581 |
+
assert abs(initial_geometry["container"]["width"] - initial_geometry["img"]["width"]) <= 2
|
| 582 |
+
assert abs(initial_geometry["uploadContainer"]["width"] - initial_geometry["img"]["width"]) <= 2
|
| 583 |
+
assert abs(initial_geometry["frame"]["width"] - initial_geometry["img"]["width"]) <= 2
|
| 584 |
+
assert initial_geometry["img"]["width"] / initial_geometry["img"]["height"] == pytest.approx(2.0, rel=0.02)
|
| 585 |
+
|
| 586 |
+
page.set_viewport_size({"width": 1024, "height": 900})
|
| 587 |
+
page.wait_for_function(
|
| 588 |
+
"""(prevWidth) => {
|
| 589 |
+
const container = document.querySelector('#live_obs .image-container');
|
| 590 |
+
const img = document.querySelector('#live_obs img');
|
| 591 |
+
if (!container || !img) return false;
|
| 592 |
+
const containerRect = container.getBoundingClientRect();
|
| 593 |
+
const imgRect = img.getBoundingClientRect();
|
| 594 |
+
return imgRect.width < prevWidth - 20 && Math.abs(containerRect.width - imgRect.width) <= 2;
|
| 595 |
+
}""",
|
| 596 |
+
arg=initial_geometry["img"]["width"],
|
| 597 |
+
timeout=10000,
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
resized_geometry = _read_live_obs_geometry(page)
|
| 601 |
+
assert resized_geometry["img"] is not None
|
| 602 |
+
assert resized_geometry["container"] is not None
|
| 603 |
+
assert resized_geometry["img"]["width"] < initial_geometry["img"]["width"] - 20
|
| 604 |
+
assert abs(resized_geometry["container"]["width"] - resized_geometry["img"]["width"]) <= 2
|
| 605 |
+
assert resized_geometry["img"]["width"] / resized_geometry["img"]["height"] == pytest.approx(2.0, rel=0.02)
|
| 606 |
+
|
| 607 |
+
box = page.locator("#live_obs img").bounding_box()
|
| 608 |
+
assert box is not None
|
| 609 |
+
target_x = box["x"] + ((36.5) / 48.0) * box["width"]
|
| 610 |
+
target_y = box["y"] + ((12.5) / 24.0) * box["height"]
|
| 611 |
+
page.mouse.click(target_x, target_y)
|
| 612 |
+
page.wait_for_function(
|
| 613 |
+
"""() => {
|
| 614 |
+
const root = document.getElementById('coords_box');
|
| 615 |
+
const field = root?.querySelector('textarea, input');
|
| 616 |
+
return !!field && /^\\d+\\s*,\\s*\\d+$/.test(field.value.trim());
|
| 617 |
+
}""",
|
| 618 |
+
timeout=5000,
|
| 619 |
+
)
|
| 620 |
+
coords_value = _read_coords_box_value(page)
|
| 621 |
+
assert coords_value is not None
|
| 622 |
+
coord_x, coord_y = [int(part.strip()) for part in coords_value.split(",", 1)]
|
| 623 |
+
assert abs(coord_x - 36) <= 1
|
| 624 |
+
assert abs(coord_y - 12) <= 1
|
| 625 |
+
|
| 626 |
+
browser.close()
|
| 627 |
+
finally:
|
| 628 |
+
server.should_exit = True
|
| 629 |
+
thread.join(timeout=10)
|
| 630 |
+
demo.close()
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
def test_live_obs_client_resize_after_hidden_phase_becomes_visible(tmp_path):
|
| 634 |
+
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
| 635 |
+
|
| 636 |
+
full_red = np.zeros((256, 256, 3), dtype=np.uint8)
|
| 637 |
+
full_red[:, :] = [255, 0, 0]
|
| 638 |
+
|
| 639 |
+
with gr.Blocks() as demo:
|
| 640 |
+
demo.css = ui_layout.CSS
|
| 641 |
+
|
| 642 |
+
show_btn = gr.Button("Show", elem_id="show_btn")
|
| 643 |
+
|
| 644 |
+
with gr.Column(visible=False, elem_id="action_phase_group") as action_phase_group:
|
| 645 |
+
gr.Image(
|
| 646 |
+
value=full_red,
|
| 647 |
+
elem_id="live_obs",
|
| 648 |
+
elem_classes=["live-obs-resizable"],
|
| 649 |
+
buttons=[],
|
| 650 |
+
sources=[],
|
| 651 |
+
)
|
| 652 |
+
|
| 653 |
+
demo.load(
|
| 654 |
+
fn=None,
|
| 655 |
+
js=ui_layout.LIVE_OBS_CLIENT_RESIZE_JS,
|
| 656 |
+
queue=False,
|
| 657 |
+
)
|
| 658 |
+
|
| 659 |
+
show_btn.click(
|
| 660 |
+
fn=lambda: gr.update(visible=True),
|
| 661 |
+
outputs=[action_phase_group],
|
| 662 |
+
queue=False,
|
| 663 |
+
)
|
| 664 |
+
|
| 665 |
+
port = _free_port()
|
| 666 |
+
host = "127.0.0.1"
|
| 667 |
+
root_url = f"http://{host}:{port}/"
|
| 668 |
+
|
| 669 |
+
app = FastAPI(title="live-obs-hidden-phase-resize-test")
|
| 670 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
| 671 |
+
|
| 672 |
+
config = uvicorn.Config(app, host=host, port=port, log_level="error")
|
| 673 |
+
server = uvicorn.Server(config)
|
| 674 |
+
thread = threading.Thread(target=server.run, daemon=True)
|
| 675 |
+
thread.start()
|
| 676 |
+
_wait_http_ready(root_url)
|
| 677 |
+
screenshot_path = tmp_path / "live_obs_hidden_phase.png"
|
| 678 |
+
|
| 679 |
+
try:
|
| 680 |
+
with sync_playwright() as p:
|
| 681 |
+
browser = p.chromium.launch(headless=True)
|
| 682 |
+
page = browser.new_page(viewport={"width": 1440, "height": 900})
|
| 683 |
+
page.goto(root_url, wait_until="domcontentloaded")
|
| 684 |
+
page.wait_for_function(
|
| 685 |
+
"() => !!window.__robommeLiveObsResizerInstalled",
|
| 686 |
+
timeout=5000,
|
| 687 |
+
)
|
| 688 |
+
|
| 689 |
+
page.click("#show_btn")
|
| 690 |
+
page.wait_for_selector("#live_obs img", timeout=10000)
|
| 691 |
+
page.wait_for_function(
|
| 692 |
+
"""() => {
|
| 693 |
+
const container = document.querySelector('#live_obs .image-container');
|
| 694 |
+
const img = document.querySelector('#live_obs img');
|
| 695 |
+
if (!container || !img) return false;
|
| 696 |
+
const containerRect = container.getBoundingClientRect();
|
| 697 |
+
const imgRect = img.getBoundingClientRect();
|
| 698 |
+
return imgRect.width > 300 && Math.abs(containerRect.width - imgRect.width) <= 2;
|
| 699 |
+
}""",
|
| 700 |
+
timeout=10000,
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
geometry = _read_live_obs_geometry(page)
|
| 704 |
+
assert geometry["container"] is not None
|
| 705 |
+
assert geometry["img"] is not None
|
| 706 |
+
assert geometry["img"]["width"] > 300
|
| 707 |
+
assert abs(geometry["container"]["width"] - geometry["img"]["width"]) <= 2
|
| 708 |
+
object_fit = page.evaluate(
|
| 709 |
+
"""() => getComputedStyle(document.querySelector('#live_obs img')).objectFit"""
|
| 710 |
+
)
|
| 711 |
+
assert object_fit == "contain"
|
| 712 |
+
|
| 713 |
+
page.locator("#live_obs img").screenshot(path=str(screenshot_path))
|
| 714 |
+
|
| 715 |
+
browser.close()
|
| 716 |
+
finally:
|
| 717 |
+
server.should_exit = True
|
| 718 |
+
thread.join(timeout=10)
|
| 719 |
+
demo.close()
|
| 720 |
+
|
| 721 |
+
screenshot = Image.open(screenshot_path).convert("RGB")
|
| 722 |
+
width, height = screenshot.size
|
| 723 |
+
samples = [
|
| 724 |
+
screenshot.getpixel((width // 2, height // 2)),
|
| 725 |
+
screenshot.getpixel((max(1, width // 10), height // 2)),
|
| 726 |
+
screenshot.getpixel((min(width - 2, (width * 9) // 10), height // 2)),
|
| 727 |
+
]
|
| 728 |
+
for pixel in samples:
|
| 729 |
+
assert pixel[0] > 200
|
| 730 |
+
assert pixel[1] < 30
|
| 731 |
+
assert pixel[2] < 30
|
| 732 |
+
|
| 733 |
+
|
| 734 |
def test_header_task_shows_env_after_init(monkeypatch):
|
| 735 |
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
| 736 |
|
gradio-web/ui_layout.py
CHANGED
|
@@ -40,10 +40,159 @@ PHASE_ACTION_KEYPOINT = "action_keypoint"
|
|
| 40 |
PHASE_EXECUTION_PLAYBACK = "execution_playback"
|
| 41 |
|
| 42 |
|
| 43 |
-
# Deprecated: no runtime JS logic in native Gradio mode.
|
| 44 |
SYNC_JS = ""
|
| 45 |
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
CSS = f"""
|
| 48 |
.native-card {{
|
| 49 |
}}
|
|
@@ -81,6 +230,14 @@ button#reference_action_btn:not(:disabled):hover,
|
|
| 81 |
background: #19713d !important;
|
| 82 |
border-color: #19713d !important;
|
| 83 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
"""
|
| 85 |
|
| 86 |
|
|
@@ -186,6 +343,7 @@ def create_ui_blocks():
|
|
| 186 |
interactive=False,
|
| 187 |
type="pil",
|
| 188 |
elem_id="live_obs",
|
|
|
|
| 189 |
show_label=True,
|
| 190 |
buttons=[],
|
| 191 |
sources=[],
|
|
@@ -514,6 +672,12 @@ def create_ui_blocks():
|
|
| 514 |
show_progress="hidden",
|
| 515 |
)
|
| 516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
demo.load(
|
| 518 |
fn=init_app_with_phase,
|
| 519 |
inputs=[],
|
|
|
|
| 40 |
PHASE_EXECUTION_PLAYBACK = "execution_playback"
|
| 41 |
|
| 42 |
|
| 43 |
+
# Deprecated: no legacy runtime JS logic in native Gradio mode.
|
| 44 |
SYNC_JS = ""
|
| 45 |
|
| 46 |
|
| 47 |
+
LIVE_OBS_CLIENT_RESIZE_JS = r"""
|
| 48 |
+
() => {
|
| 49 |
+
if (window.__robommeLiveObsResizerInstalled) {
|
| 50 |
+
if (typeof window.__robommeLiveObsSchedule === "function") {
|
| 51 |
+
window.__robommeLiveObsSchedule();
|
| 52 |
+
}
|
| 53 |
+
return;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
const state = {
|
| 57 |
+
rafId: null,
|
| 58 |
+
intervalId: null,
|
| 59 |
+
lastAppliedWidth: null,
|
| 60 |
+
lastWrapperNode: null,
|
| 61 |
+
lastFrameNode: null,
|
| 62 |
+
lastImageNode: null,
|
| 63 |
+
rootObserver: null,
|
| 64 |
+
layoutObserver: null,
|
| 65 |
+
phaseObserver: null,
|
| 66 |
+
bodyObserver: null,
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
const getTargets = () => {
|
| 70 |
+
const root = document.getElementById("live_obs");
|
| 71 |
+
if (!root) {
|
| 72 |
+
return null;
|
| 73 |
+
}
|
| 74 |
+
return {
|
| 75 |
+
root,
|
| 76 |
+
container: root.querySelector(".image-container"),
|
| 77 |
+
frame: root.querySelector(".image-frame"),
|
| 78 |
+
image: root.querySelector("img"),
|
| 79 |
+
mediaCard: document.getElementById("media_card"),
|
| 80 |
+
actionPhaseGroup: document.getElementById("action_phase_group"),
|
| 81 |
+
};
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
const applyResize = () => {
|
| 85 |
+
state.rafId = null;
|
| 86 |
+
const targets = getTargets();
|
| 87 |
+
const wrapper = targets?.root?.querySelector(".upload-container") || targets?.frame?.parentElement;
|
| 88 |
+
if (!targets || !targets.container || !wrapper || !targets.frame || !targets.image) {
|
| 89 |
+
return;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const containerWidth = Math.floor(targets.container.getBoundingClientRect().width);
|
| 93 |
+
if (!Number.isFinite(containerWidth) || containerWidth < 2) {
|
| 94 |
+
return;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
if (
|
| 98 |
+
state.lastAppliedWidth === containerWidth &&
|
| 99 |
+
state.lastWrapperNode === wrapper &&
|
| 100 |
+
state.lastFrameNode === targets.frame &&
|
| 101 |
+
state.lastImageNode === targets.image
|
| 102 |
+
) {
|
| 103 |
+
return;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
wrapper.style.width = `${containerWidth}px`;
|
| 107 |
+
wrapper.style.maxWidth = "none";
|
| 108 |
+
wrapper.style.display = "block";
|
| 109 |
+
|
| 110 |
+
targets.frame.style.width = `${containerWidth}px`;
|
| 111 |
+
targets.frame.style.maxWidth = "none";
|
| 112 |
+
targets.frame.style.display = "block";
|
| 113 |
+
|
| 114 |
+
targets.image.style.width = `${containerWidth}px`;
|
| 115 |
+
targets.image.style.maxWidth = "none";
|
| 116 |
+
targets.image.style.height = "auto";
|
| 117 |
+
targets.image.style.display = "block";
|
| 118 |
+
targets.image.style.objectFit = "contain";
|
| 119 |
+
targets.image.style.objectPosition = "center center";
|
| 120 |
+
|
| 121 |
+
state.lastAppliedWidth = containerWidth;
|
| 122 |
+
state.lastWrapperNode = wrapper;
|
| 123 |
+
state.lastFrameNode = targets.frame;
|
| 124 |
+
state.lastImageNode = targets.image;
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
const scheduleResize = () => {
|
| 128 |
+
if (state.rafId !== null) {
|
| 129 |
+
return;
|
| 130 |
+
}
|
| 131 |
+
state.rafId = window.requestAnimationFrame(applyResize);
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
const observeLiveObs = () => {
|
| 135 |
+
const targets = getTargets();
|
| 136 |
+
if (!targets) {
|
| 137 |
+
return false;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
state.rootObserver?.disconnect();
|
| 141 |
+
state.rootObserver = new MutationObserver(scheduleResize);
|
| 142 |
+
state.rootObserver.observe(targets.root, {
|
| 143 |
+
childList: true,
|
| 144 |
+
subtree: true,
|
| 145 |
+
attributes: true,
|
| 146 |
+
});
|
| 147 |
+
|
| 148 |
+
state.layoutObserver?.disconnect();
|
| 149 |
+
if (window.ResizeObserver) {
|
| 150 |
+
state.layoutObserver = new ResizeObserver(scheduleResize);
|
| 151 |
+
[targets.root, targets.container, targets.mediaCard, targets.actionPhaseGroup]
|
| 152 |
+
.filter(Boolean)
|
| 153 |
+
.forEach((node) => state.layoutObserver.observe(node));
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
state.phaseObserver?.disconnect();
|
| 157 |
+
state.phaseObserver = new MutationObserver(scheduleResize);
|
| 158 |
+
[targets.root, targets.actionPhaseGroup, targets.root.parentElement, targets.root.parentElement?.parentElement]
|
| 159 |
+
.filter(Boolean)
|
| 160 |
+
.forEach((node) =>
|
| 161 |
+
state.phaseObserver.observe(node, {
|
| 162 |
+
attributes: true,
|
| 163 |
+
attributeFilter: ["class", "style", "hidden"],
|
| 164 |
+
})
|
| 165 |
+
);
|
| 166 |
+
|
| 167 |
+
scheduleResize();
|
| 168 |
+
return true;
|
| 169 |
+
};
|
| 170 |
+
|
| 171 |
+
window.__robommeLiveObsSchedule = scheduleResize;
|
| 172 |
+
window.addEventListener("resize", scheduleResize, { passive: true });
|
| 173 |
+
document.addEventListener("visibilitychange", scheduleResize);
|
| 174 |
+
|
| 175 |
+
if (!observeLiveObs()) {
|
| 176 |
+
state.bodyObserver = new MutationObserver(() => {
|
| 177 |
+
if (observeLiveObs()) {
|
| 178 |
+
state.bodyObserver?.disconnect();
|
| 179 |
+
state.bodyObserver = null;
|
| 180 |
+
}
|
| 181 |
+
scheduleResize();
|
| 182 |
+
});
|
| 183 |
+
state.bodyObserver.observe(document.body, {
|
| 184 |
+
childList: true,
|
| 185 |
+
subtree: true,
|
| 186 |
+
});
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
state.intervalId = window.setInterval(scheduleResize, 250);
|
| 190 |
+
|
| 191 |
+
window.__robommeLiveObsResizerInstalled = true;
|
| 192 |
+
}
|
| 193 |
+
"""
|
| 194 |
+
|
| 195 |
+
|
| 196 |
CSS = f"""
|
| 197 |
.native-card {{
|
| 198 |
}}
|
|
|
|
| 230 |
background: #19713d !important;
|
| 231 |
border-color: #19713d !important;
|
| 232 |
}}
|
| 233 |
+
|
| 234 |
+
#live_obs.live-obs-resizable .image-container {{
|
| 235 |
+
width: 100%;
|
| 236 |
+
}}
|
| 237 |
+
|
| 238 |
+
#live_obs.live-obs-resizable .upload-container {{
|
| 239 |
+
width: 100%;
|
| 240 |
+
}}
|
| 241 |
"""
|
| 242 |
|
| 243 |
|
|
|
|
| 343 |
interactive=False,
|
| 344 |
type="pil",
|
| 345 |
elem_id="live_obs",
|
| 346 |
+
elem_classes=["live-obs-resizable"],
|
| 347 |
show_label=True,
|
| 348 |
buttons=[],
|
| 349 |
sources=[],
|
|
|
|
| 672 |
show_progress="hidden",
|
| 673 |
)
|
| 674 |
|
| 675 |
+
demo.load(
|
| 676 |
+
fn=None,
|
| 677 |
+
js=LIVE_OBS_CLIENT_RESIZE_JS,
|
| 678 |
+
queue=False,
|
| 679 |
+
)
|
| 680 |
+
|
| 681 |
demo.load(
|
| 682 |
fn=init_app_with_phase,
|
| 683 |
inputs=[],
|