Skip to content

Commit

Permalink
[REF-3197] Browser init workflow (#3673)
Browse files Browse the repository at this point in the history
  • Loading branch information
masenf authored Jul 19, 2024
1 parent 1bc1978 commit 1da606d
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 5 deletions.
6 changes: 5 additions & 1 deletion reflex/components/el/elements/metadata.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions reflex/constants/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
21 changes: 17 additions & 4 deletions reflex/reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
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
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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(
Expand Down
36 changes: 36 additions & 0 deletions reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import stat
import sys
import tempfile
import textwrap
import zipfile
from datetime import datetime
from fileinput import FileInput
Expand Down Expand Up @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions reflex/utils/redir.py
Original file line number Diff line number Diff line change
@@ -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"]

0 comments on commit 1da606d

Please sign in to comment.