diff --git a/reflex/components/el/elements/metadata.py b/reflex/components/el/elements/metadata.py index 77677df07..c19612abe 100644 --- a/reflex/components/el/elements/metadata.py +++ b/reflex/components/el/elements/metadata.py @@ -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 diff --git a/reflex/constants/base.py b/reflex/constants/base.py index c818fbf06..65d957d27 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -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.""" diff --git a/reflex/reflex.py b/reflex/reflex.py index a9e164477..b8a4ed03a 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -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( diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index b09c6fa23..c8455f259 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -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. diff --git a/reflex/utils/redir.py b/reflex/utils/redir.py new file mode 100644 index 000000000..461f055bb --- /dev/null +++ b/reflex/utils/redir.py @@ -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"]