| """ |
| 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() |
|
|
|
|
| |
| 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)}; |
| }} |
| """ |
|
|
| |
| 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 |
|
|