[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.""" """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.components.el.element import Element
from reflex.vars import Var as Var from reflex.vars import Var as Var
@ -64,6 +64,10 @@ class StyleEl(Element): # noqa: E742
media: Var[Union[str, int, bool]] media: Var[Union[str, int, bool]]
special_props: Set[Var] = {
Var.create_safe("suppressHydrationWarning", _var_is_string=False)
}
base = Base.create base = Base.create
head = Head.create head = Head.create

View File

@ -94,6 +94,27 @@ class Templates(SimpleNamespace):
# The default template # The default template
DEFAULT = "blank" 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): class Dirs(SimpleNamespace):
"""Folders used by the template system of Reflex.""" """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 import constants
from reflex.config import get_config from reflex.config import get_config
from reflex.custom_components.custom_components import custom_components_cli 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 # Disable typer+rich integration for help panels
typer.core.rich = False # type: ignore typer.core.rich = False # type: ignore
@ -65,6 +65,7 @@ def _init(
name: str, name: str,
template: str | None = None, template: str | None = None,
loglevel: constants.LogLevel = config.loglevel, loglevel: constants.LogLevel = config.loglevel,
ai: bool = False,
): ):
"""Initialize a new Reflex app in the given directory.""" """Initialize a new Reflex app in the given directory."""
from reflex.utils import exec, prerequisites from reflex.utils import exec, prerequisites
@ -91,8 +92,16 @@ def _init(
# Set up the web project. # Set up the web project.
prerequisites.initialize_frontend_dependencies() prerequisites.initialize_frontend_dependencies()
# Initialize the app. # Check if AI is requested and redirect the user to reflex.build.
prerequisites.initialize_app(app_name, template) 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. # Migrate Pynecone projects to Reflex.
prerequisites.migrate_to_reflex() prerequisites.migrate_to_reflex()
@ -119,9 +128,13 @@ def init(
loglevel: constants.LogLevel = typer.Option( loglevel: constants.LogLevel = typer.Option(
config.loglevel, help="The log level to use." 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.""" """Initialize a new Reflex app in the current directory."""
_init(name, template, loglevel) _init(name, template, loglevel, ai)
def _run( def _run(

View File

@ -16,6 +16,7 @@ import shutil
import stat import stat
import sys import sys
import tempfile import tempfile
import textwrap
import zipfile import zipfile
from datetime import datetime from datetime import datetime
from fileinput import FileInput from fileinput import FileInput
@ -1475,6 +1476,41 @@ def initialize_app(app_name: str, template: str | None = None):
telemetry.send("init", template=template) 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: def format_address_width(address_width) -> int | None:
"""Cast address width to an int. """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"]