Spaces:
Paused
Paused
| """ | |
| Native Gradio UI layout. | |
| Sequential media phases: Demo Video -> Action+Point -> Execute Video. | |
| Two-column layout: Point Selection | Right Panel. | |
| """ | |
| import ast | |
| import json | |
| import gradio as gr | |
| from config import ( | |
| CONTROL_PANEL_SCALE, | |
| LIVE_OBS_BASE_CLASS, | |
| LIVE_OBS_POINT_WAIT_CLASS, | |
| SESSION_CONCURRENCY_ID, | |
| SESSION_CONCURRENCY_LIMIT, | |
| SESSION_TIMEOUT, | |
| POINT_SELECTION_SCALE, | |
| RIGHT_TOP_ACTION_SCALE, | |
| RIGHT_TOP_LOG_SCALE, | |
| UI_TEXT, | |
| UI_GLOBAL_FONT_SIZE, | |
| get_live_obs_elem_classes, | |
| ) | |
| from gradio_callbacks import ( | |
| cleanup_current_request_session, | |
| cleanup_user_session, | |
| execute_step, | |
| init_app, | |
| load_next_task_wrapper, | |
| on_map_click, | |
| on_demo_video_play, | |
| on_demo_video_end_transition, | |
| on_execute_video_end_transition, | |
| on_option_select, | |
| on_reference_action, | |
| precheck_execute_inputs, | |
| restart_episode_wrapper, | |
| switch_env_wrapper, | |
| switch_to_execute_phase, | |
| touch_session, | |
| ) | |
| from user_manager import user_manager | |
| PHASE_INIT = "init" | |
| PHASE_DEMO_VIDEO = "demo_video" | |
| PHASE_ACTION_POINT = "action_point" | |
| PHASE_EXECUTION_VIDEO = "execution_video" | |
| LOAD_STATUS_MODE_IDLE = "idle" | |
| LOAD_STATUS_MODE_EPISODE_LOAD = "episode_load" | |
| APP_THEME = gr.themes.Default() | |
| # Deprecated: no legacy runtime JS logic in native Gradio mode. | |
| SYNC_JS = "" | |
| DEMO_VIDEO_PLAY_BINDING_JS = r""" | |
| () => { | |
| const bindPlayButton = () => { | |
| const button = | |
| document.querySelector("#watch_demo_video_btn button") || | |
| document.querySelector("button#watch_demo_video_btn"); | |
| if (!button || button.dataset.robommeDemoPlayBound === "1") { | |
| return; | |
| } | |
| button.dataset.robommeDemoPlayBound = "1"; | |
| button.addEventListener("click", () => { | |
| const videoEl = document.querySelector("#demo_video video"); | |
| if (!videoEl) { | |
| return; | |
| } | |
| const playPromise = videoEl.play(); | |
| if (playPromise && typeof playPromise.catch === "function") { | |
| playPromise.catch(() => {}); | |
| } | |
| }); | |
| }; | |
| if (!window.__robommeDemoPlayBindingInstalled) { | |
| const observer = new MutationObserver(() => bindPlayButton()); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| }); | |
| window.__robommeDemoPlayBindingInstalled = true; | |
| } | |
| bindPlayButton(); | |
| } | |
| """ | |
| LIVE_OBS_CLIENT_RESIZE_JS = r""" | |
| () => { | |
| if (window.__robommeLiveObsResizerInstalled) { | |
| if (typeof window.__robommeLiveObsSchedule === "function") { | |
| window.__robommeLiveObsSchedule(); | |
| } | |
| return; | |
| } | |
| const state = { | |
| rafId: null, | |
| intervalId: null, | |
| lastAppliedWidth: null, | |
| lastWrapperNode: null, | |
| lastFrameNode: null, | |
| lastImageNode: null, | |
| rootObserver: null, | |
| layoutObserver: null, | |
| phaseObserver: null, | |
| bodyObserver: null, | |
| }; | |
| const getTargets = () => { | |
| const root = document.getElementById("live_obs"); | |
| if (!root) { | |
| return null; | |
| } | |
| return { | |
| root, | |
| container: root.querySelector(".image-container"), | |
| frame: root.querySelector(".image-frame"), | |
| image: root.querySelector("img"), | |
| mediaCard: document.getElementById("media_card"), | |
| actionPhaseGroup: document.getElementById("action_phase_group"), | |
| }; | |
| }; | |
| const applyResize = () => { | |
| state.rafId = null; | |
| const targets = getTargets(); | |
| const wrapper = targets?.root?.querySelector(".upload-container") || targets?.frame?.parentElement; | |
| if (!targets || !targets.container || !wrapper || !targets.frame || !targets.image) { | |
| return; | |
| } | |
| const containerWidth = Math.floor(targets.container.getBoundingClientRect().width); | |
| if (!Number.isFinite(containerWidth) || containerWidth < 2) { | |
| return; | |
| } | |
| if ( | |
| state.lastAppliedWidth === containerWidth && | |
| state.lastWrapperNode === wrapper && | |
| state.lastFrameNode === targets.frame && | |
| state.lastImageNode === targets.image | |
| ) { | |
| return; | |
| } | |
| wrapper.style.width = `${containerWidth}px`; | |
| wrapper.style.maxWidth = "none"; | |
| wrapper.style.display = "block"; | |
| targets.frame.style.width = `${containerWidth}px`; | |
| targets.frame.style.maxWidth = "none"; | |
| targets.frame.style.display = "block"; | |
| targets.image.style.width = `${containerWidth}px`; | |
| targets.image.style.maxWidth = "none"; | |
| targets.image.style.height = "auto"; | |
| targets.image.style.display = "block"; | |
| targets.image.style.objectFit = "contain"; | |
| targets.image.style.objectPosition = "center center"; | |
| state.lastAppliedWidth = containerWidth; | |
| state.lastWrapperNode = wrapper; | |
| state.lastFrameNode = targets.frame; | |
| state.lastImageNode = targets.image; | |
| }; | |
| const scheduleResize = () => { | |
| if (state.rafId !== null) { | |
| return; | |
| } | |
| state.rafId = window.requestAnimationFrame(applyResize); | |
| }; | |
| const observeLiveObs = () => { | |
| const targets = getTargets(); | |
| if (!targets) { | |
| return false; | |
| } | |
| state.rootObserver?.disconnect(); | |
| state.rootObserver = new MutationObserver(scheduleResize); | |
| state.rootObserver.observe(targets.root, { | |
| childList: true, | |
| subtree: true, | |
| attributes: true, | |
| }); | |
| state.layoutObserver?.disconnect(); | |
| if (window.ResizeObserver) { | |
| state.layoutObserver = new ResizeObserver(scheduleResize); | |
| [targets.root, targets.container, targets.mediaCard, targets.actionPhaseGroup] | |
| .filter(Boolean) | |
| .forEach((node) => state.layoutObserver.observe(node)); | |
| } | |
| state.phaseObserver?.disconnect(); | |
| state.phaseObserver = new MutationObserver(scheduleResize); | |
| [targets.root, targets.actionPhaseGroup, targets.root.parentElement, targets.root.parentElement?.parentElement] | |
| .filter(Boolean) | |
| .forEach((node) => | |
| state.phaseObserver.observe(node, { | |
| attributes: true, | |
| attributeFilter: ["class", "style", "hidden"], | |
| }) | |
| ); | |
| scheduleResize(); | |
| return true; | |
| }; | |
| window.__robommeLiveObsSchedule = scheduleResize; | |
| window.addEventListener("resize", scheduleResize, { passive: true }); | |
| document.addEventListener("visibilitychange", scheduleResize); | |
| if (!observeLiveObs()) { | |
| state.bodyObserver = new MutationObserver(() => { | |
| if (observeLiveObs()) { | |
| state.bodyObserver?.disconnect(); | |
| state.bodyObserver = null; | |
| } | |
| scheduleResize(); | |
| }); | |
| state.bodyObserver.observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| }); | |
| } | |
| state.intervalId = window.setInterval(scheduleResize, 250); | |
| window.__robommeLiveObsResizerInstalled = true; | |
| } | |
| """ | |
| THEME_LOCK_HEAD = r""" | |
| <script> | |
| (() => { | |
| const applyLightTheme = () => { | |
| const normalizeThemeState = (store) => { | |
| try { | |
| store.setItem("theme", "light"); | |
| store.setItem("color-scheme", "light"); | |
| store.setItem("gradio-theme", "light"); | |
| for (const key of Object.keys(store)) { | |
| if (!/theme|color-scheme/i.test(key)) { | |
| continue; | |
| } | |
| const value = store.getItem(key); | |
| if (typeof value === "string" && /dark/i.test(value)) { | |
| store.setItem(key, value.replace(/dark/gi, "light")); | |
| } | |
| } | |
| } catch (error) { | |
| console.debug("Failed to normalize theme state", error); | |
| } | |
| }; | |
| const normalizeNode = (node) => { | |
| if (!node) { | |
| return; | |
| } | |
| if (node.classList.contains("dark")) { | |
| node.classList.remove("dark"); | |
| } | |
| if (node.dataset.theme !== "light") { | |
| node.dataset.theme = "light"; | |
| } | |
| if (node.getAttribute("data-color-scheme") !== "light") { | |
| node.setAttribute("data-color-scheme", "light"); | |
| } | |
| if (node.style.colorScheme !== "light") { | |
| node.style.colorScheme = "light"; | |
| } | |
| }; | |
| normalizeThemeState(window.localStorage); | |
| normalizeThemeState(window.sessionStorage); | |
| normalizeNode(document.documentElement); | |
| normalizeNode(document.body); | |
| }; | |
| applyLightTheme(); | |
| window.__robommeForceLightTheme = applyLightTheme; | |
| if (window.__robommeThemeLockInstalled) { | |
| return; | |
| } | |
| const observer = new MutationObserver(() => applyLightTheme()); | |
| observer.observe(document.documentElement, { | |
| attributes: true, | |
| attributeFilter: ["class", "data-theme", "style"], | |
| }); | |
| const attachBodyObserver = () => { | |
| if (!document.body || document.body.dataset.robommeThemeObserved === "1") { | |
| return; | |
| } | |
| document.body.dataset.robommeThemeObserved = "1"; | |
| observer.observe(document.body, { | |
| attributes: true, | |
| attributeFilter: ["class", "data-theme", "style"], | |
| }); | |
| }; | |
| attachBodyObserver(); | |
| document.addEventListener("DOMContentLoaded", attachBodyObserver, { once: true }); | |
| window.addEventListener("load", applyLightTheme, { once: true }); | |
| window.setTimeout(applyLightTheme, 0); | |
| window.setTimeout(applyLightTheme, 100); | |
| window.__robommeThemeLockInstalled = true; | |
| })(); | |
| </script> | |
| """ | |
| THEME_LOCK_JS = r""" | |
| () => { | |
| if (typeof window.__robommeForceLightTheme === "function") { | |
| window.__robommeForceLightTheme(); | |
| window.setTimeout(window.__robommeForceLightTheme, 0); | |
| } | |
| } | |
| """ | |
| SET_EPISODE_LOAD_MODE_JS = f""" | |
| () => {{ | |
| window.__robommeLoadStatusMode = {json.dumps(LOAD_STATUS_MODE_EPISODE_LOAD)}; | |
| }} | |
| """ | |
| # Gradio js preprocessors with inputs must return the original args, or Python receives None. | |
| SET_EPISODE_LOAD_MODE_IF_SWITCH_JS = f""" | |
| (uid, selectedEnv, currentTaskEnv) => {{ | |
| const normalize = (value) => (value == null ? "" : String(value).trim().toLowerCase()); | |
| const nextEnv = normalize(selectedEnv); | |
| const currentEnv = normalize(currentTaskEnv); | |
| window.__robommeLoadStatusMode = | |
| nextEnv && nextEnv !== currentEnv | |
| ? {json.dumps(LOAD_STATUS_MODE_EPISODE_LOAD)} | |
| : {json.dumps(LOAD_STATUS_MODE_IDLE)}; | |
| return [uid, selectedEnv, currentTaskEnv]; | |
| }} | |
| """ | |
| RESET_EPISODE_LOAD_MODE_JS = f""" | |
| () => {{ | |
| window.__robommeLoadStatusMode = {json.dumps(LOAD_STATUS_MODE_IDLE)}; | |
| }} | |
| """ | |
| PROGRESS_TEXT_REWRITE_JS = f""" | |
| () => {{ | |
| const modeEpisodeLoad = {json.dumps(LOAD_STATUS_MODE_EPISODE_LOAD)}; | |
| const modeIdle = {json.dumps(LOAD_STATUS_MODE_IDLE)}; | |
| const overlayStateIdle = "idle"; | |
| const overlayStateEpisodeLoad = "episode-load"; | |
| const overlayStateQueue = "queue"; | |
| const episodeLoadingText = {json.dumps(UI_TEXT["progress"]["episode_loading"])}; | |
| const queueWaitText = {json.dumps(UI_TEXT["progress"]["queue_wait"])}; | |
| const elapsedOnlyPattern = /^\\d+(?:\\.\\d+)?s$/i; | |
| const progressStyleKeys = [ | |
| "position", | |
| "top", | |
| "right", | |
| "bottom", | |
| "left", | |
| "transform", | |
| "width", | |
| "max-width", | |
| "display", | |
| "margin", | |
| "text-align", | |
| "color", | |
| "font-size", | |
| "font-weight", | |
| "line-height", | |
| "white-space", | |
| "z-index", | |
| "order", | |
| ]; | |
| const wrapStyleKeys = ["flex-direction", "gap"]; | |
| const spinnerStyleKeys = ["order"]; | |
| window.__robommeLoadStatusMode = window.__robommeLoadStatusMode || modeIdle; | |
| const getMode = () => window.__robommeLoadStatusMode || modeIdle; | |
| const isEpisodeLoadRaw = (raw) => | |
| elapsedOnlyPattern.test(raw) || /^processing/i.test(raw); | |
| const isQueueRaw = (raw) => /^queue:/i.test(raw); | |
| const clearInlineStyles = (node, keys) => {{ | |
| if (!(node instanceof HTMLElement)) {{ | |
| return; | |
| }} | |
| keys.forEach((key) => node.style.removeProperty(key)); | |
| }}; | |
| const ensureOverlayStyles = () => {{ | |
| const host = document.getElementById("native_progress_host"); | |
| if (!(host instanceof HTMLElement)) {{ | |
| return null; | |
| }} | |
| host.style.setProperty("position", "fixed", "important"); | |
| host.style.setProperty("inset", "0", "important"); | |
| host.style.setProperty("z-index", "9999", "important"); | |
| host.style.setProperty("pointer-events", "none", "important"); | |
| host.style.setProperty("width", "100vw", "important"); | |
| host.style.setProperty("height", "100vh", "important"); | |
| host.style.setProperty("min-height", "100vh", "important"); | |
| host.style.setProperty("overflow", "visible", "important"); | |
| const wrap = host.querySelector(".wrap"); | |
| if (wrap instanceof HTMLElement) {{ | |
| wrap.style.setProperty("position", "fixed", "important"); | |
| wrap.style.setProperty("inset", "0", "important"); | |
| wrap.style.setProperty("width", "100vw", "important"); | |
| wrap.style.setProperty("height", "100vh", "important"); | |
| wrap.style.setProperty("min-height", "100vh", "important"); | |
| wrap.style.setProperty("padding", "0", "important"); | |
| wrap.style.setProperty("display", "flex", "important"); | |
| wrap.style.setProperty("align-items", "center", "important"); | |
| wrap.style.setProperty("justify-content", "center", "important"); | |
| wrap.style.setProperty("background", "rgba(255, 255, 255, 0.92)", "important"); | |
| wrap.style.setProperty("backdrop-filter", "blur(2px)", "important"); | |
| }} | |
| const markdown = host.querySelector('[data-testid="markdown"]'); | |
| const prose = | |
| markdown instanceof HTMLElement | |
| ? markdown.querySelector(".prose, .md") || markdown | |
| : null; | |
| const markdownWrapper = | |
| markdown instanceof HTMLElement && markdown.parentElement instanceof HTMLElement | |
| ? markdown.parentElement | |
| : null; | |
| const spinner = | |
| host.querySelector("svg") instanceof SVGElement | |
| ? host.querySelector("svg").closest("div") | |
| : null; | |
| if (markdown instanceof HTMLElement) {{ | |
| markdown.style.setProperty("position", "fixed", "important"); | |
| markdown.style.setProperty("inset", "0", "important"); | |
| markdown.style.setProperty("display", "flex", "important"); | |
| markdown.style.setProperty("align-items", "center", "important"); | |
| markdown.style.setProperty("justify-content", "center", "important"); | |
| markdown.style.setProperty("padding", "24px", "important"); | |
| }} | |
| if (prose instanceof HTMLElement) {{ | |
| prose.style.setProperty("width", "min(720px, calc(100vw - 48px))", "important"); | |
| prose.style.setProperty("max-width", "calc(100vw - 48px)", "important"); | |
| prose.style.setProperty("margin", "0", "important"); | |
| prose.style.setProperty("padding", "0", "important"); | |
| prose.style.setProperty("text-align", "center", "important"); | |
| prose.style.setProperty("color", "#0f172a", "important"); | |
| prose.style.setProperty("font-size", "var(--text-lg)", "important"); | |
| prose.style.setProperty("font-weight", "600", "important"); | |
| prose.style.setProperty("line-height", "1.5", "important"); | |
| prose.style.setProperty("white-space", "pre-line", "important"); | |
| }} | |
| return {{ | |
| wrap, | |
| markdown, | |
| prose, | |
| markdownWrapper, | |
| spinner, | |
| }}; | |
| }}; | |
| const resolveRawText = (node) => {{ | |
| const displayed = (node.innerText || node.textContent || "").trim(); | |
| return node.dataset.robommeProgressRaw || displayed; | |
| }}; | |
| const setProgressNodeVisible = (node, visible) => {{ | |
| node.style.setProperty("visibility", visible ? "visible" : "hidden", "important"); | |
| node.style.setProperty("opacity", visible ? "1" : "0", "important"); | |
| }}; | |
| const resetProgressNode = (node) => {{ | |
| const raw = node.dataset.robommeProgressRaw || (node.innerText || node.textContent || "").trim(); | |
| if (raw) {{ | |
| node.textContent = raw; | |
| }} | |
| delete node.dataset.robommeProgressCustomized; | |
| delete node.dataset.robommeProgressRaw; | |
| delete node.dataset.robommeProgressCustom; | |
| clearInlineStyles(node, progressStyleKeys); | |
| setProgressNodeVisible(node, true); | |
| }}; | |
| const applyEpisodeLoadLayout = (entry, overlayRefs) => {{ | |
| const node = entry.node; | |
| const {{ | |
| wrap, | |
| spinner, | |
| }} = overlayRefs || {{}}; | |
| if (wrap instanceof HTMLElement) {{ | |
| wrap.style.setProperty("flex-direction", "column", "important"); | |
| wrap.style.setProperty("gap", "24px", "important"); | |
| }} | |
| if (spinner instanceof HTMLElement) {{ | |
| spinner.style.setProperty("order", "1", "important"); | |
| }} | |
| node.dataset.robommeProgressCustomized = "1"; | |
| node.dataset.robommeProgressCustom = episodeLoadingText; | |
| node.textContent = episodeLoadingText; | |
| setProgressNodeVisible(node, true); | |
| node.style.setProperty("position", "static", "important"); | |
| node.style.setProperty("top", "auto", "important"); | |
| node.style.setProperty("right", "auto", "important"); | |
| node.style.setProperty("bottom", "auto", "important"); | |
| node.style.setProperty("left", "auto", "important"); | |
| node.style.setProperty("transform", "none", "important"); | |
| node.style.setProperty("width", "min(720px, calc(100vw - 48px))", "important"); | |
| node.style.setProperty("max-width", "calc(100vw - 48px)", "important"); | |
| node.style.setProperty("display", "block", "important"); | |
| node.style.setProperty("margin", "0", "important"); | |
| node.style.setProperty("text-align", "center", "important"); | |
| node.style.setProperty("color", "#0f172a", "important"); | |
| node.style.setProperty("font-size", "var(--text-lg)", "important"); | |
| node.style.setProperty("font-weight", "600", "important"); | |
| node.style.setProperty("line-height", "1.5", "important"); | |
| node.style.setProperty("white-space", "pre-line", "important"); | |
| node.style.setProperty("z-index", "20", "important"); | |
| node.style.setProperty("order", "2", "important"); | |
| }}; | |
| const rewriteNode = (entry, overlayState) => {{ | |
| const node = entry.node; | |
| if (!(node instanceof HTMLElement)) {{ | |
| return; | |
| }} | |
| const raw = entry.raw; | |
| if (getMode() !== modeEpisodeLoad) {{ | |
| resetProgressNode(node); | |
| if (isEpisodeLoadRaw(raw) || isQueueRaw(raw)) {{ | |
| node.textContent = ""; | |
| }} | |
| return; | |
| }} | |
| node.dataset.robommeProgressRaw = raw; | |
| if (overlayState === overlayStateEpisodeLoad && isEpisodeLoadRaw(raw)) {{ | |
| applyEpisodeLoadLayout(entry, window.__robommeOverlayRefs || null); | |
| return; | |
| }} | |
| if (overlayState === overlayStateQueue && isQueueRaw(raw)) {{ | |
| const custom = `${{queueWaitText}} | ${{raw}}`; | |
| node.dataset.robommeProgressCustomized = "1"; | |
| node.dataset.robommeProgressCustom = custom; | |
| if ((node.innerText || node.textContent || "").trim() !== custom) {{ | |
| node.textContent = custom; | |
| }} | |
| setProgressNodeVisible(node, true); | |
| return; | |
| }} | |
| resetProgressNode(node); | |
| }}; | |
| const syncOverlayContent = (overlayRefs, overlayState) => {{ | |
| if (!overlayRefs) {{ | |
| return; | |
| }} | |
| const {{ | |
| wrap, | |
| markdown, | |
| prose, | |
| markdownWrapper, | |
| spinner, | |
| }} = overlayRefs; | |
| const markdownText = | |
| prose instanceof HTMLElement ? ((prose.innerText || prose.textContent || "").trim()) : ""; | |
| const showMarkdown = overlayState === overlayStateIdle && markdownText.length > 0; | |
| if (wrap instanceof HTMLElement && overlayState !== overlayStateEpisodeLoad) {{ | |
| clearInlineStyles(wrap, wrapStyleKeys); | |
| }} | |
| if (spinner instanceof HTMLElement && overlayState !== overlayStateEpisodeLoad) {{ | |
| clearInlineStyles(spinner, spinnerStyleKeys); | |
| }} | |
| if (markdownWrapper instanceof HTMLElement) {{ | |
| markdownWrapper.style.setProperty("display", showMarkdown ? "block" : "none", "important"); | |
| markdownWrapper.style.setProperty("visibility", showMarkdown ? "visible" : "hidden", "important"); | |
| }} | |
| if (markdown instanceof HTMLElement) {{ | |
| markdown.style.setProperty("display", showMarkdown ? "flex" : "none", "important"); | |
| markdown.style.setProperty("visibility", showMarkdown ? "visible" : "hidden", "important"); | |
| }} | |
| }}; | |
| const rewriteAll = () => {{ | |
| const overlayRefs = ensureOverlayStyles(); | |
| window.__robommeOverlayRefs = overlayRefs; | |
| const progressEntries = Array.from(document.querySelectorAll(".progress-text")) | |
| .filter((node) => node instanceof HTMLElement) | |
| .map((node) => {{ | |
| const raw = resolveRawText(node); | |
| return {{ | |
| node, | |
| raw, | |
| }}; | |
| }}); | |
| let overlayState = overlayStateIdle; | |
| if (getMode() === modeEpisodeLoad) {{ | |
| const hasEpisodeLoad = progressEntries.some((entry) => isEpisodeLoadRaw(entry.raw)); | |
| const hasQueue = progressEntries.some((entry) => isQueueRaw(entry.raw)); | |
| if (hasEpisodeLoad) {{ | |
| overlayState = overlayStateEpisodeLoad; | |
| }} else if (hasQueue) {{ | |
| overlayState = overlayStateQueue; | |
| }} | |
| }} | |
| progressEntries.forEach((entry) => rewriteNode(entry, overlayState)); | |
| syncOverlayContent(overlayRefs, overlayState); | |
| }}; | |
| const scheduleRewrite = () => {{ | |
| if (window.__robommeProgressRewriteRaf) {{ | |
| return; | |
| }} | |
| window.__robommeProgressRewriteRaf = window.requestAnimationFrame(() => {{ | |
| window.__robommeProgressRewriteRaf = null; | |
| rewriteAll(); | |
| }}); | |
| }}; | |
| if (!window.__robommeProgressRewriteInstalled) {{ | |
| const observer = new MutationObserver(scheduleRewrite); | |
| observer.observe(document.body, {{ | |
| childList: true, | |
| subtree: true, | |
| characterData: true, | |
| }}); | |
| window.setInterval(scheduleRewrite, 200); | |
| window.__robommeProgressRewriteInstalled = true; | |
| }} | |
| scheduleRewrite(); | |
| }} | |
| """ | |
| CSS = f""" | |
| :root {{ | |
| --body-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --prose-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --input-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --block-label-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --block-title-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --block-info-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --checkbox-label-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --button-large-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --button-medium-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --button-small-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --section-header-text-size: {UI_GLOBAL_FONT_SIZE} !important; | |
| --text-md: {UI_GLOBAL_FONT_SIZE} !important; | |
| color-scheme: light !important; | |
| }} | |
| .native-card {{ | |
| }} | |
| #header_title h2 {{ | |
| font-size: var(--text-xxl) !important; | |
| }} | |
| #native_progress_host {{ | |
| position: fixed !important; | |
| inset: 0 !important; | |
| z-index: 9999 !important; | |
| pointer-events: none !important; | |
| }} | |
| #native_progress_host .wrap {{ | |
| width: 100vw !important; | |
| min-height: 100vh !important; | |
| }} | |
| #native_progress_host .wrap.translucent {{ | |
| background: rgba(255, 255, 255, 0.92) !important; | |
| backdrop-filter: blur(2px); | |
| }} | |
| #native_progress_host .pending {{ | |
| min-height: 100vh !important; | |
| }} | |
| #reference_action_btn button:not(:disabled), | |
| button#reference_action_btn:not(:disabled) {{ | |
| background: #1f8b4c !important; | |
| border-color: #1f8b4c !important; | |
| color: #ffffff !important; | |
| }} | |
| #reference_action_btn button:not(:disabled):hover, | |
| button#reference_action_btn:not(:disabled):hover {{ | |
| background: #19713d !important; | |
| border-color: #19713d !important; | |
| }} | |
| #live_obs.live-obs-resizable .image-container {{ | |
| width: 100%; | |
| }} | |
| #live_obs.live-obs-resizable .upload-container {{ | |
| width: 100%; | |
| }} | |
| #watch_demo_video_btn, | |
| #watch_demo_video_btn button, | |
| button#watch_demo_video_btn {{ | |
| width: 100%; | |
| }} | |
| #media_card {{ | |
| --media-card-radius: 8px; | |
| position: relative; | |
| border-radius: var(--media-card-radius); | |
| overflow: visible; | |
| }} | |
| #media_card > div, | |
| #media_card #action_phase_group, | |
| #media_card #video_phase_group, | |
| #media_card #execution_video_group, | |
| #media_card #live_obs, | |
| #media_card #live_obs button, | |
| #media_card #live_obs .image-frame, | |
| #media_card #live_obs img, | |
| #media_card #demo_video, | |
| #media_card #demo_video video, | |
| #media_card #execute_video, | |
| #media_card #execute_video video {{ | |
| border-radius: var(--media-card-radius); | |
| }} | |
| #media_card::after {{ | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| border: 3px solid rgba(225, 29, 72, 0.00); | |
| box-sizing: border-box; | |
| box-shadow: 0 0 0 0 rgba(225, 29, 72, 0.00); | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 180ms ease, border-color 180ms ease, box-shadow 180ms ease; | |
| z-index: 0; | |
| }} | |
| @keyframes media-card-point-ring {{ | |
| 0% {{ | |
| box-shadow: 0 0 0 0 rgba(225, 29, 72, 0.00); | |
| border-color: rgba(225, 29, 72, 0.72); | |
| opacity: 0.72; | |
| }} | |
| 18% {{ | |
| box-shadow: 0 0 0 4px rgba(225, 29, 72, 0.28); | |
| border-color: rgba(225, 29, 72, 0.94); | |
| opacity: 1; | |
| }} | |
| 36% {{ | |
| box-shadow: 0 0 0 10px rgba(225, 29, 72, 0.12); | |
| border-color: rgba(225, 29, 72, 0.88); | |
| opacity: 0.94; | |
| }} | |
| 62% {{ | |
| box-shadow: 0 0 0 2px rgba(225, 29, 72, 0.18); | |
| border-color: rgba(225, 29, 72, 0.96); | |
| opacity: 1; | |
| }} | |
| 100% {{ | |
| box-shadow: 0 0 0 0 rgba(225, 29, 72, 0.00); | |
| border-color: rgba(225, 29, 72, 0.72); | |
| opacity: 0.72; | |
| }} | |
| }} | |
| #media_card:has(#live_obs.{LIVE_OBS_POINT_WAIT_CLASS})::after {{ | |
| border-color: rgba(225, 29, 72, 0.94); | |
| box-shadow: 0 0 0 0 rgba(225, 29, 72, 0.00); | |
| opacity: 1; | |
| animation: media-card-point-ring 1.2s ease-in-out infinite; | |
| }} | |
| """ | |
| def extract_last_goal(goal_text): | |
| """Extract last goal from goal text that may be a list representation.""" | |
| if not goal_text: | |
| return "" | |
| text = goal_text.strip() | |
| if text.startswith("[") and text.endswith("]"): | |
| try: | |
| goals = ast.literal_eval(text) | |
| if isinstance(goals, list) and goals: | |
| for goal in reversed(goals): | |
| goal_text = str(goal).strip() | |
| if goal_text: | |
| return goal_text | |
| except Exception: | |
| pass | |
| return text.split("\n")[0].strip() | |
| def capitalize_first_letter(text): | |
| """Uppercase only the first character for display.""" | |
| if not text: | |
| return text | |
| if len(text) == 1: | |
| return text.upper() | |
| return text[0].upper() + text[1:] | |
| def render_header_goal(goal_text): | |
| """Render header goal from raw goal text using display-only normalization.""" | |
| last_goal = extract_last_goal(goal_text or "") | |
| if not last_goal: | |
| return "—" | |
| return capitalize_first_letter(last_goal) | |
| def _phase_from_updates(main_interface_update, demo_video_phase_update): | |
| if isinstance(main_interface_update, dict) and main_interface_update.get("visible") is False: | |
| return PHASE_INIT | |
| if isinstance(demo_video_phase_update, dict) and demo_video_phase_update.get("visible") is True: | |
| return PHASE_DEMO_VIDEO | |
| return PHASE_ACTION_POINT | |
| def _with_phase_from_load(load_result): | |
| load_result = _normalize_load_result(load_result) | |
| phase = _phase_from_updates(load_result[1], load_result[15]) | |
| return ( | |
| *load_result, | |
| phase, | |
| gr.update(value=""), | |
| ) | |
| def _with_rejected_init(load_result, message): | |
| load_result = _normalize_load_result(load_result) | |
| return ( | |
| *load_result, | |
| PHASE_INIT, | |
| gr.update(value=message), | |
| ) | |
| def _normalize_load_result(load_result): | |
| if not isinstance(load_result, (tuple, list)): | |
| return load_result | |
| normalized = list(load_result) | |
| if len(normalized) in {19, 20}: | |
| normalized.insert(8, gr.update(value=None, visible=False, playback_position=0)) | |
| normalized.insert(16, gr.update(visible=False)) | |
| if len(normalized) == 21: | |
| normalized.append( | |
| { | |
| "preserve_terminal_log": False, | |
| "terminal_log_value": None, | |
| } | |
| ) | |
| return tuple(normalized) | |
| def _phase_visibility_updates(phase): | |
| if phase == PHASE_DEMO_VIDEO: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| ) | |
| if phase == PHASE_EXECUTION_VIDEO: | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| ) | |
| if phase == PHASE_ACTION_POINT: | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=True), | |
| ) | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| ) | |
| def create_ui_blocks(): | |
| """构建 Gradio Blocks,并完成页面阶段状态(phase)的联动绑定。""" | |
| def render_header_task(task_text): | |
| clean_task = str(task_text or "").strip() | |
| if not clean_task: | |
| return None | |
| if clean_task.lower().startswith("current task:"): | |
| clean_task = clean_task.split(":", 1)[1].strip() | |
| marker = " (Episode " | |
| if marker in clean_task: | |
| clean_task = clean_task.split(marker, 1)[0].strip() | |
| return " ".join(clean_task.splitlines()).strip() or None | |
| with gr.Blocks(title="Oracle Planner Interface") as demo: | |
| demo.css = CSS | |
| demo.theme = APP_THEME | |
| demo.head = THEME_LOCK_HEAD | |
| gr.Markdown("## 🔥 RoboMME Interactive Demo 🚀🚀🚀", elem_id="header_title") | |
| gr.Markdown("### Curious about memory-based robotic tasks? 🤖 Try it yourself 👇") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| header_task_box = gr.Dropdown( | |
| choices=list(user_manager.env_choices), | |
| value=render_header_task(""), | |
| label="Current Task 📚", | |
| show_label=True, | |
| interactive=True, | |
| elem_id="header_task", | |
| ) | |
| with gr.Column(scale=2): | |
| header_goal_box = gr.Textbox( | |
| value=render_header_goal(""), | |
| label="Task Goal 🏆", | |
| show_label=True, | |
| interactive=False, | |
| lines=1, | |
| elem_id="header_goal", | |
| ) | |
| uid_state = gr.State( | |
| value=None, | |
| time_to_live=SESSION_TIMEOUT, | |
| delete_callback=cleanup_user_session, | |
| ) | |
| ui_phase_state = gr.State(value=PHASE_INIT) | |
| post_execute_controls_state = gr.State( | |
| value={ | |
| "exec_btn_interactive": True, | |
| "reference_action_interactive": True, | |
| } | |
| ) | |
| post_execute_log_state = gr.State( | |
| value={ | |
| "preserve_terminal_log": False, | |
| "terminal_log_value": None, | |
| "preserve_execute_video_log": False, | |
| "execute_video_log_value": None, | |
| } | |
| ) | |
| current_task_env_state = gr.State(value=None) | |
| suppress_next_option_change_state = gr.State(value=False) | |
| task_info_box = gr.Textbox(visible=False, elem_id="task_info_box") | |
| progress_info_box = gr.Textbox(visible=False) | |
| goal_box = gr.Textbox(visible=False) | |
| with gr.Column(visible=True, elem_id="main_interface_root") as main_interface: | |
| native_progress_host = gr.Markdown( | |
| value="", | |
| visible=True, | |
| container=False, | |
| elem_id="native_progress_host", | |
| ) | |
| with gr.Row(elem_id="main_layout_row"): | |
| with gr.Column(scale=POINT_SELECTION_SCALE): | |
| with gr.Column(elem_classes=["native-card"], elem_id="media_card"): | |
| with gr.Column(visible=False, elem_id="video_phase_group") as video_phase_group: | |
| video_display = gr.Video( | |
| label="Video Playback 🎬", | |
| interactive=False, | |
| elem_id="demo_video", | |
| autoplay=False, | |
| show_label=True, | |
| visible=True, | |
| ) | |
| watch_demo_video_btn = gr.Button( | |
| "Watch Video Input 🎬", | |
| variant="primary", | |
| size="lg", | |
| interactive=False, | |
| visible=False, | |
| elem_id="watch_demo_video_btn", | |
| ) | |
| with gr.Column(visible=False, elem_id="execution_video_group") as execution_video_group: | |
| execute_video_display = gr.Video( | |
| label="Execution Playback 🎬", | |
| interactive=False, | |
| elem_id="execute_video", | |
| autoplay=True, | |
| show_label=True, | |
| visible=True, | |
| ) | |
| with gr.Column(visible=False, elem_id="action_phase_group") as action_phase_group: | |
| img_display = gr.Image( | |
| label="Point Selection", | |
| interactive=False, | |
| type="pil", | |
| elem_id="live_obs", | |
| elem_classes=get_live_obs_elem_classes(), | |
| show_label=True, | |
| buttons=[], | |
| sources=[], | |
| ) | |
| with gr.Column(scale=CONTROL_PANEL_SCALE): | |
| with gr.Column(visible=False, elem_id="control_panel_group") as control_panel_group: | |
| with gr.Row(elem_id="right_top_row", equal_height=False): | |
| with gr.Column(scale=RIGHT_TOP_ACTION_SCALE, elem_id="right_action_col"): | |
| with gr.Column(elem_classes=["native-card"], elem_id="action_selection_card"): | |
| options_radio = gr.Radio( | |
| choices=[], | |
| label="Action Selection 🦾", | |
| type="value", | |
| show_label=True, | |
| elem_id="action_radio", | |
| ) | |
| coords_box = gr.Textbox( | |
| label="Coords", | |
| value="", | |
| interactive=False, | |
| show_label=False, | |
| visible=False, | |
| elem_id="coords_box", | |
| ) | |
| with gr.Column(scale=RIGHT_TOP_LOG_SCALE, elem_id="right_log_col"): | |
| with gr.Column(elem_classes=["native-card"], elem_id="log_card"): | |
| log_output = gr.Textbox( | |
| value="", | |
| lines=4, | |
| max_lines=None, | |
| show_label=True, | |
| interactive=False, | |
| elem_id="log_output", | |
| label="System Log 📝", | |
| ) | |
| with gr.Row(elem_id="action_buttons_row"): | |
| with gr.Column(elem_classes=["native-card", "native-button-card"], elem_id="exec_btn_card"): | |
| exec_btn = gr.Button("EXECUTE 🤖", variant="stop", size="lg", elem_id="exec_btn") | |
| with gr.Column( | |
| elem_classes=["native-card", "native-button-card"], | |
| elem_id="reference_btn_card", | |
| ): | |
| reference_action_btn = gr.Button( | |
| "Ground Truth Action 🙋♀️", | |
| variant="secondary", | |
| interactive=False, | |
| elem_id="reference_action_btn", | |
| ) | |
| with gr.Column( | |
| elem_classes=["native-card", "native-button-card"], | |
| elem_id="restart_episode_btn_card", | |
| ): | |
| restart_episode_btn = gr.Button( | |
| "Restart Episode 🔄", | |
| variant="secondary", | |
| interactive=False, | |
| elem_id="restart_episode_btn", | |
| ) | |
| with gr.Column( | |
| elem_classes=["native-card", "native-button-card"], | |
| elem_id="next_task_btn_card", | |
| ): | |
| next_task_btn = gr.Button( | |
| "Change Episode ⏩️", | |
| variant="primary", | |
| interactive=False, | |
| elem_id="next_task_btn", | |
| ) | |
| with gr.Accordion( | |
| "Task Hint💡", | |
| open=False, | |
| visible=True, | |
| elem_classes=["native-card"], | |
| elem_id="task_hint_card", | |
| ): | |
| task_hint_display = gr.Textbox( | |
| value="", | |
| lines=8, | |
| max_lines=16, | |
| show_label=False, | |
| interactive=True, | |
| elem_id="task_hint_display", | |
| ) | |
| load_flow_outputs = [ | |
| uid_state, | |
| main_interface, | |
| img_display, | |
| log_output, | |
| options_radio, | |
| goal_box, | |
| coords_box, | |
| video_display, | |
| execute_video_display, | |
| watch_demo_video_btn, | |
| task_info_box, | |
| progress_info_box, | |
| restart_episode_btn, | |
| next_task_btn, | |
| exec_btn, | |
| video_phase_group, | |
| execution_video_group, | |
| action_phase_group, | |
| control_panel_group, | |
| task_hint_display, | |
| reference_action_btn, | |
| post_execute_log_state, | |
| ui_phase_state, | |
| native_progress_host, | |
| ] | |
| phase_visibility_outputs = [ | |
| video_phase_group, | |
| execution_video_group, | |
| action_phase_group, | |
| control_panel_group, | |
| ] | |
| action_queue_kwargs = { | |
| "concurrency_id": SESSION_CONCURRENCY_ID, | |
| "concurrency_limit": SESSION_CONCURRENCY_LIMIT, | |
| } | |
| def _skip_load_flow(): | |
| return tuple(gr.skip() for _ in range(len(load_flow_outputs))) | |
| def _coerce_init_load_result(result): | |
| if isinstance(result, dict): | |
| status = result.get("status") | |
| if status == "ready": | |
| return _with_phase_from_load(result.get("load_result")) | |
| if status == "rejected": | |
| return _with_rejected_init( | |
| result.get("load_result"), | |
| result.get("message", UI_TEXT["progress"]["entry_rejected"]), | |
| ) | |
| return _with_phase_from_load(result) | |
| def _normalize_env_choice(env_value, choices): | |
| if env_value is None: | |
| return None | |
| env_text = str(env_value).strip() | |
| if not env_text: | |
| return None | |
| lower_map = {} | |
| for choice in choices: | |
| choice_text = str(choice).strip() | |
| if choice_text: | |
| lower_map.setdefault(choice_text.lower(), choice_text) | |
| return lower_map.get(env_text.lower(), env_text) | |
| def _resolve_header_task_state(task_text, fallback_env=None): | |
| base_choices = list(user_manager.env_choices) | |
| parsed_env = render_header_task(task_text) | |
| selected_env = _normalize_env_choice(parsed_env, base_choices) | |
| if selected_env is None: | |
| selected_env = _normalize_env_choice(fallback_env, base_choices) | |
| choices = list(base_choices) | |
| if selected_env and selected_env not in choices: | |
| choices.append(selected_env) | |
| return choices, selected_env | |
| def _build_header_task_update(task_text, fallback_env=None): | |
| choices, selected_env = _resolve_header_task_state(task_text, fallback_env=fallback_env) | |
| return gr.update(choices=choices, value=selected_env) | |
| def sync_header_from_task(task_text, goal_text): | |
| _, selected_env = _resolve_header_task_state(task_text) | |
| return _build_header_task_update(task_text), render_header_goal(goal_text), selected_env | |
| def sync_header_from_goal(goal_text, task_text, current_header_task): | |
| _, selected_env = _resolve_header_task_state(task_text, fallback_env=current_header_task) | |
| return ( | |
| _build_header_task_update(task_text, fallback_env=current_header_task), | |
| render_header_goal(goal_text), | |
| selected_env, | |
| ) | |
| def init_app_with_phase(request: gr.Request): | |
| return _coerce_init_load_result(init_app(request)) | |
| def load_next_task_with_phase(uid): | |
| return _with_phase_from_load(load_next_task_wrapper(uid)) | |
| def restart_episode_with_phase(uid): | |
| return _with_phase_from_load(restart_episode_wrapper(uid)) | |
| def switch_env_with_phase(uid, selected_env): | |
| return _with_phase_from_load(switch_env_wrapper(uid, selected_env)) | |
| def _normalize_selected_env(selected_env, current_task_env): | |
| base_choices = list(user_manager.env_choices) | |
| normalized_selected_env = _normalize_env_choice(selected_env, base_choices) | |
| normalized_current_env = _normalize_env_choice(current_task_env, base_choices) | |
| return normalized_selected_env, normalized_current_env | |
| def maybe_switch_env_with_phase(uid, selected_env, current_task_env): | |
| normalized_selected_env, normalized_current_env = _normalize_selected_env( | |
| selected_env, | |
| current_task_env, | |
| ) | |
| if not normalized_selected_env or normalized_selected_env == normalized_current_env: | |
| return _skip_load_flow() | |
| return switch_env_with_phase(uid, normalized_selected_env) | |
| task_info_box.change( | |
| fn=sync_header_from_task, | |
| inputs=[task_info_box, goal_box], | |
| outputs=[header_task_box, header_goal_box, current_task_env_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| goal_box.change( | |
| fn=sync_header_from_goal, | |
| inputs=[goal_box, task_info_box, header_task_box], | |
| outputs=[header_task_box, header_goal_box, current_task_env_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| header_task_switch = header_task_box.select( | |
| fn=maybe_switch_env_with_phase, | |
| inputs=[uid_state, header_task_box, current_task_env_state], | |
| outputs=load_flow_outputs, | |
| show_progress="full", | |
| js=SET_EPISODE_LOAD_MODE_IF_SWITCH_JS, | |
| show_progress_on=[native_progress_host], | |
| **action_queue_kwargs, | |
| ).then( | |
| fn=_phase_visibility_updates, | |
| inputs=[ui_phase_state], | |
| outputs=phase_visibility_outputs, | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=sync_header_from_task, | |
| inputs=[task_info_box, goal_box], | |
| outputs=[header_task_box, header_goal_box, current_task_env_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| header_task_switch.success( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| header_task_switch.failure( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| next_task_click = next_task_btn.click( | |
| fn=load_next_task_with_phase, | |
| inputs=[uid_state], | |
| outputs=load_flow_outputs, | |
| show_progress="full", | |
| js=SET_EPISODE_LOAD_MODE_JS, | |
| show_progress_on=[native_progress_host], | |
| **action_queue_kwargs, | |
| ).then( | |
| fn=_phase_visibility_updates, | |
| inputs=[ui_phase_state], | |
| outputs=phase_visibility_outputs, | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=sync_header_from_task, | |
| inputs=[task_info_box, goal_box], | |
| outputs=[header_task_box, header_goal_box, current_task_env_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| next_task_click.success( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| next_task_click.failure( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| restart_episode_click = restart_episode_btn.click( | |
| fn=restart_episode_with_phase, | |
| inputs=[uid_state], | |
| outputs=load_flow_outputs, | |
| show_progress="full", | |
| js=SET_EPISODE_LOAD_MODE_JS, | |
| show_progress_on=[native_progress_host], | |
| **action_queue_kwargs, | |
| ).then( | |
| fn=_phase_visibility_updates, | |
| inputs=[ui_phase_state], | |
| outputs=phase_visibility_outputs, | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=sync_header_from_task, | |
| inputs=[task_info_box, goal_box], | |
| outputs=[header_task_box, header_goal_box, current_task_env_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| restart_episode_click.success( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| restart_episode_click.failure( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| video_display.end( | |
| fn=on_demo_video_end_transition, | |
| inputs=[uid_state, ui_phase_state], | |
| outputs=[ | |
| video_phase_group, | |
| action_phase_group, | |
| control_panel_group, | |
| log_output, | |
| watch_demo_video_btn, | |
| ui_phase_state, | |
| ], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| video_display.stop( | |
| fn=on_demo_video_end_transition, | |
| inputs=[uid_state, ui_phase_state], | |
| outputs=[ | |
| video_phase_group, | |
| action_phase_group, | |
| control_panel_group, | |
| log_output, | |
| watch_demo_video_btn, | |
| ui_phase_state, | |
| ], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| execute_video_display.end( | |
| fn=on_execute_video_end_transition, | |
| inputs=[uid_state, post_execute_controls_state, post_execute_log_state], | |
| outputs=[ | |
| execution_video_group, | |
| action_phase_group, | |
| control_panel_group, | |
| options_radio, | |
| exec_btn, | |
| restart_episode_btn, | |
| next_task_btn, | |
| img_display, | |
| log_output, | |
| reference_action_btn, | |
| task_hint_display, | |
| post_execute_log_state, | |
| ui_phase_state, | |
| ], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| execute_video_display.stop( | |
| fn=on_execute_video_end_transition, | |
| inputs=[uid_state, post_execute_controls_state, post_execute_log_state], | |
| outputs=[ | |
| execution_video_group, | |
| action_phase_group, | |
| control_panel_group, | |
| options_radio, | |
| exec_btn, | |
| restart_episode_btn, | |
| next_task_btn, | |
| img_display, | |
| log_output, | |
| reference_action_btn, | |
| task_hint_display, | |
| post_execute_log_state, | |
| ui_phase_state, | |
| ], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| img_display.select( | |
| fn=on_map_click, | |
| inputs=[uid_state, options_radio], | |
| outputs=[img_display, coords_box, log_output], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| options_radio.change( | |
| fn=on_option_select, | |
| inputs=[uid_state, options_radio, coords_box, suppress_next_option_change_state, post_execute_log_state], | |
| outputs=[coords_box, img_display, log_output, suppress_next_option_change_state, post_execute_log_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| watch_demo_video_btn.click( | |
| fn=on_demo_video_play, | |
| inputs=[uid_state], | |
| outputs=[watch_demo_video_btn], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| reference_action_btn.click( | |
| fn=on_reference_action, | |
| inputs=[uid_state, options_radio], | |
| outputs=[img_display, options_radio, coords_box, log_output, suppress_next_option_change_state], | |
| **action_queue_kwargs, | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| exec_btn.click( | |
| fn=precheck_execute_inputs, | |
| inputs=[uid_state, options_radio, coords_box], | |
| outputs=[], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=switch_to_execute_phase, | |
| inputs=[uid_state], | |
| outputs=[ | |
| options_radio, | |
| exec_btn, | |
| restart_episode_btn, | |
| next_task_btn, | |
| img_display, | |
| reference_action_btn, | |
| task_hint_display, | |
| ], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=execute_step, | |
| inputs=[uid_state, options_radio, coords_box], | |
| outputs=[ | |
| img_display, | |
| log_output, | |
| task_info_box, | |
| progress_info_box, | |
| restart_episode_btn, | |
| next_task_btn, | |
| exec_btn, | |
| execute_video_display, | |
| action_phase_group, | |
| control_panel_group, | |
| execution_video_group, | |
| options_radio, | |
| coords_box, | |
| reference_action_btn, | |
| task_hint_display, | |
| post_execute_controls_state, | |
| post_execute_log_state, | |
| ui_phase_state, | |
| ], | |
| show_progress="hidden", | |
| **action_queue_kwargs, | |
| ).then( | |
| fn=_phase_visibility_updates, | |
| inputs=[ui_phase_state], | |
| outputs=phase_visibility_outputs, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| demo.load( | |
| fn=None, | |
| js=THEME_LOCK_JS, | |
| queue=False, | |
| ) | |
| demo.load( | |
| fn=None, | |
| js=LIVE_OBS_CLIENT_RESIZE_JS, | |
| queue=False, | |
| ) | |
| demo.load( | |
| fn=None, | |
| js=DEMO_VIDEO_PLAY_BINDING_JS, | |
| queue=False, | |
| ) | |
| demo.load( | |
| fn=None, | |
| js=PROGRESS_TEXT_REWRITE_JS, | |
| queue=False, | |
| ) | |
| init_load = demo.load( | |
| fn=init_app_with_phase, | |
| inputs=[], | |
| outputs=load_flow_outputs, | |
| show_progress="full", | |
| js=SET_EPISODE_LOAD_MODE_JS, | |
| show_progress_on=[native_progress_host], | |
| queue=False, | |
| ).then( | |
| fn=_phase_visibility_updates, | |
| inputs=[ui_phase_state], | |
| outputs=phase_visibility_outputs, | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=sync_header_from_task, | |
| inputs=[task_info_box, goal_box], | |
| outputs=[header_task_box, header_goal_box, current_task_env_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ).then( | |
| fn=touch_session, | |
| inputs=[uid_state], | |
| outputs=[uid_state], | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| init_load.success( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| init_load.failure( | |
| fn=None, | |
| js=RESET_EPISODE_LOAD_MODE_JS, | |
| queue=False, | |
| show_progress="hidden", | |
| ) | |
| demo.unload(fn=cleanup_current_request_session) | |
| demo.queue(max_size=None, default_concurrency_limit=None) | |
| return demo | |