Quellcode durchsuchen

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

Masen Furer vor 10 Monaten
Ursprung
Commit
1da606dd8e

+ 5 - 1
reflex/components/el/elements/metadata.py

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

+ 21 - 0
reflex/constants/base.py

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

+ 17 - 4
reflex/reflex.py

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

+ 36 - 0
reflex/utils/prerequisites.py

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