[REF-3197] Browser init workflow (#3673)
This commit is contained in:
parent
1bc1978c31
commit
1da606dd8e
@ -1,6 +1,6 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
|
||||
from typing import Union
|
||||
from typing import Set, Union
|
||||
|
||||
from reflex.components.el.element import Element
|
||||
from reflex.vars import Var as Var
|
||||
@ -64,6 +64,10 @@ class StyleEl(Element): # noqa: E742
|
||||
|
||||
media: Var[Union[str, int, bool]]
|
||||
|
||||
special_props: Set[Var] = {
|
||||
Var.create_safe("suppressHydrationWarning", _var_is_string=False)
|
||||
}
|
||||
|
||||
|
||||
base = Base.create
|
||||
head = Head.create
|
||||
|
@ -94,6 +94,27 @@ class Templates(SimpleNamespace):
|
||||
# The default template
|
||||
DEFAULT = "blank"
|
||||
|
||||
# The reflex.build frontend host
|
||||
REFLEX_BUILD_FRONTEND = os.environ.get(
|
||||
"REFLEX_BUILD_FRONTEND", "https://flexgen.reflex.run"
|
||||
)
|
||||
|
||||
# The reflex.build backend host
|
||||
REFLEX_BUILD_BACKEND = os.environ.get(
|
||||
"REFLEX_BUILD_BACKEND", "https://rxh-prod-flexgen.fly.dev"
|
||||
)
|
||||
|
||||
# The URL to redirect to reflex.build
|
||||
REFLEX_BUILD_URL = (
|
||||
REFLEX_BUILD_FRONTEND + "/gen?reflex_init_token={reflex_init_token}"
|
||||
)
|
||||
|
||||
# The URL to poll waiting for the user to select a generation.
|
||||
REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
|
||||
|
||||
# The URL to fetch the generation's reflex code
|
||||
REFLEX_BUILD_CODE_URL = REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}"
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
"""Folders used by the template system of Reflex."""
|
||||
|
||||
|
@ -16,7 +16,7 @@ from reflex_cli.utils import dependency
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.custom_components.custom_components import custom_components_cli
|
||||
from reflex.utils import console, telemetry
|
||||
from reflex.utils import console, redir, telemetry
|
||||
|
||||
# Disable typer+rich integration for help panels
|
||||
typer.core.rich = False # type: ignore
|
||||
@ -65,6 +65,7 @@ def _init(
|
||||
name: str,
|
||||
template: str | None = None,
|
||||
loglevel: constants.LogLevel = config.loglevel,
|
||||
ai: bool = False,
|
||||
):
|
||||
"""Initialize a new Reflex app in the given directory."""
|
||||
from reflex.utils import exec, prerequisites
|
||||
@ -91,8 +92,16 @@ def _init(
|
||||
# Set up the web project.
|
||||
prerequisites.initialize_frontend_dependencies()
|
||||
|
||||
# Initialize the app.
|
||||
prerequisites.initialize_app(app_name, template)
|
||||
# Check if AI is requested and redirect the user to reflex.build.
|
||||
if ai:
|
||||
prerequisites.initialize_app(app_name, template=constants.Templates.DEFAULT)
|
||||
generation_hash = redir.reflex_build_redirect()
|
||||
prerequisites.initialize_main_module_index_from_generation(
|
||||
app_name, generation_hash=generation_hash
|
||||
)
|
||||
else:
|
||||
# Initialize the app.
|
||||
prerequisites.initialize_app(app_name, template)
|
||||
|
||||
# Migrate Pynecone projects to Reflex.
|
||||
prerequisites.migrate_to_reflex()
|
||||
@ -119,9 +128,13 @@ def init(
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
ai: bool = typer.Option(
|
||||
False,
|
||||
help="Use AI to create the initial template. Cannot be used with existing app or `--template` option.",
|
||||
),
|
||||
):
|
||||
"""Initialize a new Reflex app in the current directory."""
|
||||
_init(name, template, loglevel)
|
||||
_init(name, template, loglevel, ai)
|
||||
|
||||
|
||||
def _run(
|
||||
|
@ -16,6 +16,7 @@ import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from fileinput import FileInput
|
||||
@ -1475,6 +1476,41 @@ def initialize_app(app_name: str, template: str | None = None):
|
||||
telemetry.send("init", template=template)
|
||||
|
||||
|
||||
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
|
||||
"""Overwrite the `index` function in the main module with reflex.build generated code.
|
||||
|
||||
Args:
|
||||
app_name: The name of the app.
|
||||
generation_hash: The generation hash from reflex.build.
|
||||
"""
|
||||
# Download the reflex code for the generation.
|
||||
resp = httpx.get(
|
||||
constants.Templates.REFLEX_BUILD_CODE_URL.format(
|
||||
generation_hash=generation_hash
|
||||
)
|
||||
).raise_for_status()
|
||||
|
||||
def replace_content(_match):
|
||||
return "\n".join(
|
||||
[
|
||||
"def index() -> rx.Component:",
|
||||
textwrap.indent("return " + resp.text, " "),
|
||||
"",
|
||||
"",
|
||||
],
|
||||
)
|
||||
|
||||
main_module_path = Path(app_name, app_name + constants.Ext.PY)
|
||||
main_module_code = main_module_path.read_text()
|
||||
main_module_path.write_text(
|
||||
re.sub(
|
||||
r"def index\(\).*:\n([^\n]\s+.*\n+)+",
|
||||
replace_content,
|
||||
main_module_code,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def format_address_width(address_width) -> int | None:
|
||||
"""Cast address width to an int.
|
||||
|
||||
|
48
reflex/utils/redir.py
Normal file
48
reflex/utils/redir.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""Utilities to handle redirection to browser UI."""
|
||||
|
||||
import time
|
||||
import uuid
|
||||
import webbrowser
|
||||
|
||||
import httpx
|
||||
|
||||
from .. import constants
|
||||
from . import console
|
||||
|
||||
|
||||
def open_browser_and_wait(
|
||||
target_url: str, poll_url: str, interval: int = 1
|
||||
) -> httpx.Response:
|
||||
"""Open a browser window to target_url and request poll_url until it returns successfully.
|
||||
|
||||
Args:
|
||||
target_url: The URL to open in the browser.
|
||||
poll_url: The URL to poll for success.
|
||||
interval: The interval in seconds to wait between polling.
|
||||
|
||||
Returns:
|
||||
The response from the poll_url.
|
||||
"""
|
||||
if not webbrowser.open(target_url):
|
||||
console.warn(
|
||||
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
|
||||
)
|
||||
console.info("Complete the workflow in the browser to continue.")
|
||||
while response := httpx.get(poll_url, follow_redirects=True):
|
||||
if response.is_success:
|
||||
break
|
||||
time.sleep(interval)
|
||||
return response
|
||||
|
||||
|
||||
def reflex_build_redirect() -> str:
|
||||
"""Open the browser window to reflex.build and wait for the user to select a generation.
|
||||
|
||||
Returns:
|
||||
The selected generation hash.
|
||||
"""
|
||||
token = str(uuid.uuid4())
|
||||
target_url = constants.Templates.REFLEX_BUILD_URL.format(reflex_init_token=token)
|
||||
poll_url = constants.Templates.REFLEX_BUILD_POLL_URL.format(reflex_init_token=token)
|
||||
response = open_browser_and_wait(target_url, poll_url)
|
||||
return response.json()["generation_hash"]
|
Loading…
Reference in New Issue
Block a user