[REF-3197] Browser init workflow (#3673)

This commit is contained in:
Masen Furer 2024-07-18 18:51:33 -07:00 committed by GitHub
parent 1bc1978c31
commit 1da606dd8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 127 additions and 5 deletions

View File

@ -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

View File

@ -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."""

View File

@ -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(

View File

@ -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
View 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"]