diff --git a/docs/ai_builder/features/ide.md b/docs/ai_builder/features/ide.md index 0d7113f79fe..816c128f7c0 100644 --- a/docs/ai_builder/features/ide.md +++ b/docs/ai_builder/features/ide.md @@ -9,7 +9,7 @@ import reflex as rx ```python eval rx.el.div( rx.video( - url="https://www.youtube.com/embed/UAj9vUweQ5g", + src="https://www.youtube.com/embed/UAj9vUweQ5g", width="100%", height="400px", ), diff --git a/docs/ai_builder/files.md b/docs/ai_builder/files.md index 3c313e8b5c1..8ab95e3f80c 100644 --- a/docs/ai_builder/files.md +++ b/docs/ai_builder/files.md @@ -2,7 +2,7 @@ To upload a file to the AI Builder click the `📎 Attach` button and select the file you want to upload from your computer. You can also drag and drop files directly into the chat window. -This section does not cover uploading images. Check out [Images](/docs/ai-builder/images/) to learn more about uploading images. +This section does not cover uploading images. Check out [Images](/docs/ai/images/) to learn more about uploading images. ```md alert ## Supported File Types @@ -31,4 +31,4 @@ The AI Builder currently supports the following file types for upload and proces The files you upload will automatically be added to the `assets/` folder of your app, and the AI Builder will be able to read and process their contents as part of your prompts. -The maximum number of files you can upload at a time is `5`. The maximum file size for uploads is `5MB`. If you need to work with larger files, consider breaking them into smaller chunks or using external storage solutions and linking to them via APIs. \ No newline at end of file +The maximum number of files you can upload at a time is `5`. The maximum file size for uploads is `5MB`. If you need to work with larger files, consider breaking them into smaller chunks or using external storage solutions and linking to them via APIs. diff --git a/docs/ai_builder/integrations/ai_onboarding.md b/docs/ai_builder/integrations/ai_onboarding.md new file mode 100644 index 00000000000..16dc484b96f --- /dev/null +++ b/docs/ai_builder/integrations/ai_onboarding.md @@ -0,0 +1,176 @@ +# AI Onboarding + +```python exec +import reflex as rx + + +def _resource_card( + title: str, body: str, href: str, action: str, target: str = "_self" +) -> rx.Component: + return rx.el.a( + rx.el.div( + rx.el.h3(title, class_name="text-base font-semibold text-secondary-12"), + rx.el.p(body, class_name="text-sm leading-6 text-secondary-11"), + rx.el.div(action, class_name="text-sm font-semibold text-primary-10"), + class_name="flex h-full flex-col gap-2 rounded-lg border border-secondary-a4 bg-white-1 p-4 transition-colors hover:bg-secondary-2 shadow-xs", + ), + href=href, + target=target, + class_name="no-underline", + ) + + +def onboarding_resources() -> rx.Component: + return rx.el.div( + _resource_card( + "Docs for Agents", + "Give your agent current Reflex documentation as Markdown, llms.txt, or structured MCP context.", + "/llms.txt", + "Open llms.txt", + target="_blank", + ), + _resource_card( + "MCP", + "Connect supported AI tools to Reflex documentation and component information through MCP.", + "/ai/integrations/mcp-overview/", + "View MCP overview", + ), + _resource_card( + "Skills", + "Install Reflex Agent Skills so assistants follow Reflex-specific workflows for docs, setup, and process management.", + "/ai/integrations/skills/", + "Install skills", + ), + _resource_card( + "Reflex Build", + "Use Reflex Build when you want an AI-native environment for creating, editing, previewing, and shipping apps.", + "/ai/overview/best-practices/", + "Read best practices", + ), + class_name="grid grid-cols-1 gap-3 md:grid-cols-2 my-6", + ) +``` + +Everything you need to onboard your AI coding assistant to Reflex. + +If you are building Reflex apps with AI, combine current docs, structured MCP context, and local skills so the assistant can plan, code, run, and debug with the same assumptions as the Reflex docs. + +```python eval +onboarding_resources() +``` + +## Prerequisite: Choose Your Workflow + +You do not need an API key to read Reflex documentation. Start by deciding how your assistant will work with Reflex: + +- For local app development, use Python 3.10 or newer and a project virtual environment. +- For current documentation context, give the assistant Markdown docs or `llms.txt`. +- For structured tool access, use the Reflex MCP integration. +- For repeatable agent behavior, install Reflex Agent Skills. +- For a browser-based AI builder, use Reflex Build. + +## Reflex Docs for Agents + +You can give your assistant current Reflex documentation in a few ways. + +`````md tabs + +## Markdown Pages + +Every docs page has a Markdown version that agents can read directly. Add `.md` to the docs path: + +```text +https://reflex.dev/docs/ai/integrations/ai-onboarding.md +``` + +Use this when an agent needs one focused page. + + +## llms.txt + +Use the generated docs index when an agent needs a broad map of Reflex docs: + +```text +https://reflex.dev/docs/llms.txt +``` + +The index groups docs by section and links to agent-friendly Markdown assets. + + +## MCP + +Use MCP when your editor or agent can call tools for structured documentation and component lookup: + +```text +https://mcp.reflex.dev/mcp +``` + +See the [MCP overview](/docs/ai/integrations/mcp-overview/) and [MCP installation](/docs/ai/integrations/mcp-installation/) guides for details. + +````` + +## Reflex MCP + +The Reflex MCP integration gives supported AI tools structured access to Reflex framework docs and component information. Use it when your assistant can connect to an MCP server and benefit from tool-assisted lookup while editing code. + +```md alert warning +# The Reflex MCP integration is currently only available for enterprise customers. Please [book a demo](https://reflex.dev/pricing/) to discuss access. +``` + +## Reflex Agent Skills + +Reflex Agent Skills are local instruction packs that teach AI assistants how to work with Reflex projects. They cover: + +- Current Reflex docs and concept lookup. +- Python environment setup for Reflex projects. +- Reflex compile, run, restart, and debugging workflows. + +Install the skill pack from the [reflex-dev/agent-skills](https://github.com/reflex-dev/agent-skills) repository, or follow the [Skills installation guide](/docs/ai/integrations/skills/). + +```bash +npx skills add reflex-dev/agent-skills +``` + +## Quick Start Prompts + +Use these prompts to give your agent a strong starting point. + +`````md tabs + +## New App + +```text +Create a new Reflex app. Use current Reflex documentation, set up a Python 3.10+ virtual environment, initialize the project, and validate it with reflex compile --dry before handing it back. +``` + + +## Existing App + +```text +Work on this existing Reflex app. First inspect the project structure and current dependencies. Use Reflex docs for current APIs, make the requested change, then run reflex compile --dry or the project's existing test command. +``` + + +## Debugging + +```text +Debug this Reflex app. Read the error and relevant logs, identify whether the failure is in imports, state, event handlers, routing, components, or runtime setup, then apply the smallest fix and re-run validation. +``` + +````` + +## Reflex Build + +Reflex Build is the AI-native way to create Reflex apps in the browser. Use it when you want to generate, edit, preview, and share apps without setting up a local environment first. + +Start with the [Reflex Build best practices](/docs/ai/overview/best-practices/) guide, then use MCP and Skills when you want your local assistant to keep working with the same Reflex concepts. + +## Recommended Validation + +Ask your assistant to validate Reflex changes before it hands work back: + +```bash +reflex compile --dry +``` + +For running apps, pair that with the process-management workflow from Reflex Agent Skills so the assistant restarts only the server process it owns. diff --git a/docs/ai_builder/integrations/skills.md b/docs/ai_builder/integrations/skills.md new file mode 100644 index 00000000000..8c1e4ab707f --- /dev/null +++ b/docs/ai_builder/integrations/skills.md @@ -0,0 +1,318 @@ +# Skills + +```python exec +import reflex as rx + + +def _summary_card(kicker: str, title: str, body: str) -> rx.Component: + return rx.el.div( + rx.el.div(kicker, class_name="text-xs font-semibold uppercase text-primary-10"), + rx.el.h3(title, class_name="text-base font-semibold text-secondary-12"), + rx.el.p(body, class_name="text-sm leading-6 text-secondary-11"), + class_name="flex flex-col gap-2 rounded-lg border border-secondary-a4 bg-white-1 p-4", + ) + + +def skills_summary_cards() -> rx.Component: + return rx.el.div( + _summary_card( + "Docs", + "Current Reflex guidance", + "Point agents to the right Reflex docs for state, vars, components, routing, styling, deployment, and more.", + ), + _summary_card( + "Setup", + "Python environment workflow", + "Teach agents how to create a virtual environment, install Reflex, and initialize a new project safely.", + ), + _summary_card( + "Runtime", + "Process management", + "Give agents a repeatable way to compile, run, restart, and debug Reflex apps without guessing at processes.", + ), + _summary_card( + "Context", + "Pairs well with MCP", + "Use skills for durable local instructions and MCP for structured lookup of current docs and component data.", + ), + class_name="grid grid-cols-1 gap-3 md:grid-cols-2 my-6", + ) +``` + +Reflex Agent Skills give AI coding assistants up-to-date guidance for building Reflex applications. They package Reflex-specific knowledge, setup steps, and process-management workflows into reusable `SKILL.md` files that agents can load when the conversation or codebase calls for them. + +The skills are maintained in the [reflex-dev/agent-skills](https://github.com/reflex-dev/agent-skills) repository and are designed for agents that support the Agent Skills standard, including Claude Code, Cursor, OpenCode, OpenAI Codex, and Pi. + +```python eval +skills_summary_cards() +``` + +## When to Use Skills + +Use Reflex Agent Skills when you want an AI assistant to follow Reflex-specific workflows instead of relying only on general training data. They are especially useful when an assistant needs to: + +- Build or edit a Reflex app. +- Set up a new Python environment. +- Decide which Reflex docs apply to the task. +- Compile, run, restart, or debug a local Reflex server. +- Keep generated code aligned with current Reflex patterns. + +Skills load contextually. For example, when an assistant sees a Python file importing `reflex as rx`, it can load the Reflex docs skill. When you ask it to start a new app, it can load the Python environment setup skill before running project commands. + +## Skills vs MCP + +Skills and MCP solve related but different problems. For the best experience, use both. + +```md definition +# Skills + +Local instruction packs that tell the agent how to work with Reflex, which workflows to follow, and which references matter for common development tasks. + +# MCP + +Structured runtime access to Reflex documentation and component information through a hosted server. Use it when an agent or editor can call MCP tools directly. +``` + +```md alert info +# Use Skills for durable local guidance. Use MCP for structured documentation lookup and richer tool-assisted context. +``` + +## Installation + +Choose the install path that matches your assistant. + +`````md tabs + +## Claude Code + +Install the plugin from the Claude Code plugin marketplace: + +```text +/plugin marketplace add reflex-dev/agent-skills +/plugin install reflex@reflex-agent-skills +``` + +Restart or refresh Claude Code after installation if the skills do not appear immediately. + + +## Cursor + +Install from the Cursor Marketplace, or add the repository manually from: + +```text +reflex-dev/agent-skills +``` + +In Cursor, add it through **Settings > Rules > Add Rule > Remote Rule (GitHub)**. + + +## CLI + +If your environment supports the `skills` CLI, install the package with: + +```bash +npx skills add reflex-dev/agent-skills +``` + +Use the CLI's update command later to keep the skill pack current. + + +## Manual + +Clone the repository: + +```bash +git clone https://github.com/reflex-dev/agent-skills.git +``` + +Then copy the folders inside `skills/` into the appropriate location: + +| Agent | Skill Directory | +| --- | --- | +| Claude Code | `~/.claude/skills/` | +| Cursor | `~/.cursor/skills/` | +| OpenCode | `~/.config/opencode/skills/` | +| OpenAI Codex | `~/.codex/skills/` | +| Pi | `~/.pi/agent/skills/` | + +Copy the skill folders themselves, not the parent `skills/` directory. + +````` + +## Included Skills + +The Reflex skill pack includes three core skills. + +`````md tabs + +## Docs + +The `reflex-docs` skill gives the assistant a Reflex-specific reference map and a summary of the framework's core concepts. + +### Use this skill when the assistant is: + +- Building full-stack Python web apps with Reflex. +- Writing files that import `reflex` or use the `rx` namespace. +- Creating components. +- Managing state, vars, computed vars, or event handlers. +- Working with routing, styling, database models, assets, authentication, client storage, API routes, custom components, or wrapped React components. + +### It points the assistant to docs for: + +- Core app structure: getting started, components, state and vars, events, pages, and routing. +- UI and styling: styling, assets, the component library, and recipes. +- Backend features: database models, authentication, client storage, API routes, and API reference. +- Advanced integrations: custom components and wrapped React components. + +It also reminds the assistant to prefer current Reflex documentation over pre-trained knowledge when there is a conflict. + + +## Setup + +The `setup-python-env` skill guides the assistant through setting up a Python environment for a Reflex app. + +### Use this skill when: + +- Starting a new Reflex project. +- Setting up a development environment. +- There is no `.venv` directory. +- Reflex imports fail because dependencies are missing. + +### Preferred workflow: + +```bash +uv venv .venv +source .venv/bin/activate +uv add reflex +reflex init +``` + +If `uv` is not available, the skill falls back to: + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install reflex +reflex init +``` + +The workflow checks for an existing `.venv`, verifies Python 3.10 or newer, and installs Reflex only when needed. + + +## Process + +The `reflex-process-management` skill teaches the assistant how to compile, run, reload, and debug Reflex apps. + +### Use this skill when: + +- Testing that a Reflex app compiles. +- Starting a local Reflex server. +- Restarting a server after code changes. +- Reading logs to diagnose app errors. + +### Validation command: + +```bash +reflex compile --dry +``` + +### Run command: + +```bash +reflex run --env prod --single-port 2>&1 | tee reflex.log +``` + +Production mode does not hot reload. To apply code changes, the assistant should stop the listening process for the app port and restart the server: + +```bash +lsof -i : -sTCP:LISTEN -t +kill -INT $(lsof -i : -sTCP:LISTEN -t) +``` + +Using `-sTCP:LISTEN` helps the assistant target the server process instead of browser connections. + +````` + +## Recommended Workflow + +`````md tabs + +## New App + +1. Install Reflex Agent Skills in your assistant. +2. Ask the assistant to create or initialize a Reflex app. +3. The assistant should load `setup-python-env`. +4. After the environment is ready, the assistant can run `reflex init`. +5. As the app is built, the assistant should use `reflex-docs` for current framework guidance. +6. Before handing the app back, the assistant should use `reflex-process-management` to compile or run the project. + + +## Existing + +1. Open your Reflex project in an agent-enabled editor. +2. Ask for the feature, bug fix, or refactor you want. +3. The assistant should load `reflex-docs` when it sees Reflex code. +4. For local verification, the assistant should compile the app with `reflex compile --dry` or run it with the process-management workflow. + + +## Debugging + +1. Ask the assistant to inspect the error. +2. The assistant should read `reflex.log` if the app was started through the process-management workflow. +3. The assistant should identify the failing import, component, event handler, route, or state update. +4. After applying a fix, the assistant should restart the app if it is running in production mode. + +````` + +## Keeping Skills Updated + +Because Reflex evolves quickly, update the skill pack regularly. + +If you installed through a marketplace or skills CLI, update through that same tool. If you cloned the repository manually, pull the latest changes: + +```bash +cd agent-skills +git pull +``` + +Then copy the updated skill folders back into your assistant's skills directory if your setup does not read directly from the cloned repository. + +## Troubleshooting + +`````md tabs + +## Loading + +Check that: + +- Your assistant supports Agent Skills. +- The skill folders are in the correct skills directory. +- Each skill folder contains a `SKILL.md` file. +- The assistant was restarted or refreshed after installation if required. + + +## Outdated Advice + +Ask the assistant to use the Reflex docs skill, or pair the skill pack with the Reflex MCP integration for structured access to current docs and component information. + + +## Commands + +Ask the assistant to follow the `setup-python-env` skill again and verify: + +- The virtual environment is active. +- Python is version 3.10 or newer. +- Reflex is installed in the active environment. +- The command is being run from the project root. + + +## App Updates + +If the app was started with `--env prod`, it will not hot reload. Restart the server with the `reflex-process-management` workflow. + +````` + +## Contributing + +Each skill lives in `skills//` and contains a `SKILL.md` manifest. To contribute new guidance, update or add a skill in the [reflex-dev/agent-skills](https://github.com/reflex-dev/agent-skills) repository and follow the [Agent Skills spec](https://agentskills.io/) for the skill format. diff --git a/docs/ai_builder/overview/best_practices.md b/docs/ai_builder/overview/best_practices.md index 0c0eeb2fe83..761c3a26b95 100644 --- a/docs/ai_builder/overview/best_practices.md +++ b/docs/ai_builder/overview/best_practices.md @@ -174,7 +174,7 @@ This enables a hybrid workflow: generate your app and make major changes in the - **No local installation** — Hosted MCP server requires no additional Python packages - **Secure authentication** — OAuth 2.1 integration with your Reflex account -For complete setup instructions for Claude Desktop, Windsurf, Codex, and other MCP clients, visit our [MCP integration](https://reflex.dev/docs/ai-builder/integrations/mcp-installation/) documentation. +For complete setup instructions for Claude Desktop, Windsurf, Codex, and other MCP clients, visit our [MCP integration](https://reflex.dev/docs/ai/integrations/mcp-installation/) documentation. --- diff --git a/docs/app/agent_files/_plugin.py b/docs/app/agent_files/_plugin.py index 487e777976f..05bc22f2ebf 100644 --- a/docs/app/agent_files/_plugin.py +++ b/docs/app/agent_files/_plugin.py @@ -1,46 +1,295 @@ +import re +from collections import OrderedDict from collections.abc import Sequence -from pathlib import Path, PosixPath +from dataclasses import dataclass +from pathlib import Path from reflex.constants import Dirs from reflex_base.plugins import CommonContext, Plugin from typing_extensions import Unpack +MCP_DOC_PATHS = { + "ai/integrations/mcp-installation.md", + "ai/integrations/mcp-overview.md", +} +AI_ONBOARDING_DOC_PATHS = { + "ai/integrations/ai-onboarding.md", +} +MCP_DOC_ORDER = { + "ai/integrations/mcp-overview.md": 0, + "ai/integrations/mcp-installation.md": 1, +} +SKILLS_DOC_PATHS = { + "ai/integrations/skills.md", +} + +LLMS_FULL_INTRO = """\ +# Reflex Documentation +Source: {docs_home_url} + +This file stitches together the full Reflex documentation as Markdown for AI agents and LLM indexing. + +For a navigable index with links to individual docs pages, see [llms.txt]({llms_txt_url}). +""" + + +@dataclass(frozen=True) +class MarkdownFileEntry: + """A generated markdown file and its llms.txt metadata.""" + + url_path: Path + source_path: Path + title: str + section: str + + +def _format_title(value: str) -> str: + """Format a route or file segment as a title.""" + words = re.split(r"[-_\s]+", value) + acronyms = { + "ag": "AG", + "ai": "AI", + "api": "API", + "cli": "CLI", + "css": "CSS", + "html": "HTML", + "ide": "IDE", + "mcp": "MCP", + "ui": "UI", + "url": "URL", + "urls": "URLs", + } + return " ".join(acronyms.get(word.lower(), word.capitalize()) for word in words) + + +def _extract_markdown_title(source: str) -> str | None: + """Extract the first top-level heading from markdown source.""" + in_frontmatter = source.startswith("---\n") + in_code_block = False + + for line in source.splitlines(): + stripped = line.strip() + if in_frontmatter: + if stripped == "---": + in_frontmatter = False + continue + if stripped.startswith("```"): + in_code_block = not in_code_block + continue + if in_code_block: + continue + if stripped.startswith("# "): + return stripped.removeprefix("# ").strip() + return None + + +def _llms_url_for_path(url_path: Path) -> str: + """Return the public URL for a generated markdown asset.""" + from reflex_base.config import get_config + + config = get_config() + deploy_url = config.deploy_url.removesuffix("/") if config.deploy_url else "" + frontend_path = (config.frontend_path or "").strip("/") + base_url = deploy_url + if frontend_path: + base_url = f"{base_url}/{frontend_path}" if base_url else f"/{frontend_path}" + return ( + f"{base_url}/{url_path.as_posix()}" if base_url else f"/{url_path.as_posix()}" + ) + + +def _docs_home_url() -> str: + """Return the public URL for the docs home.""" + from reflex_base.config import get_config + + config = get_config() + deploy_url = config.deploy_url.removesuffix("/") if config.deploy_url else "" + frontend_path = (config.frontend_path or "").strip("/") + if deploy_url and frontend_path: + return f"{deploy_url}/{frontend_path}/" + if deploy_url: + return f"{deploy_url}/" + if frontend_path: + return f"/{frontend_path}/" + return "/" + + +def _strip_first_heading(source: str) -> str: + """Remove the first top-level markdown heading from a page body.""" + lines = source.strip().splitlines() + in_frontmatter = bool(lines) and lines[0].strip() == "---" + in_code_block = False + + for index, line in enumerate(lines): + stripped = line.strip() + if in_frontmatter: + if stripped == "---" and index > 0: + in_frontmatter = False + continue + if stripped.startswith("```"): + in_code_block = not in_code_block + continue + if in_code_block: + continue + if stripped.startswith("# "): + return "\n".join([*lines[:index], *lines[index + 1 :]]).strip() + return source.strip() -def generate_markdown_files() -> tuple[tuple[Path, str | bytes], ...]: - from reflex_docs.pages.docs import doc_markdown_sources + +def _include_in_llms_txt(markdown_file: MarkdownFileEntry) -> bool: + """Return whether a markdown file should appear in llms.txt.""" + path = markdown_file.url_path.as_posix() + return ( + path in MCP_DOC_PATHS + or path in AI_ONBOARDING_DOC_PATHS + or path in SKILLS_DOC_PATHS + or not path.startswith("ai/") + or path.startswith("ai/overview/") + ) + + +def _section_for_path(url_path: Path) -> str: + """Return the llms.txt section for a generated markdown asset.""" + path = url_path.as_posix() + if path in AI_ONBOARDING_DOC_PATHS: + return "AI Onboarding" + if path in MCP_DOC_PATHS: + return "MCP" + if path in SKILLS_DOC_PATHS: + return "Skills" + if path.startswith("ai/"): + return "AI Builder" + return _format_title(path.split("/", maxsplit=1)[0]) + + +def _ordered_sections( + sections: OrderedDict[str, list[MarkdownFileEntry]], +) -> list[str]: + """Return sections in display order.""" + ordered_sections = list(sections) + if "AI Builder" in sections and "MCP" in sections: + ordered_sections.remove("MCP") + ordered_sections.insert(ordered_sections.index("AI Builder") + 1, "MCP") + if "AI Builder" in sections and "AI Onboarding" in sections: + ordered_sections.remove("AI Onboarding") + ordered_sections.insert( + ordered_sections.index("AI Builder") + 1, "AI Onboarding" + ) + if "MCP" in sections and "Skills" in sections: + ordered_sections.remove("Skills") + ordered_sections.insert(ordered_sections.index("MCP") + 1, "Skills") + elif "AI Builder" in sections and "Skills" in sections: + ordered_sections.remove("Skills") + ordered_sections.insert(ordered_sections.index("AI Builder") + 1, "Skills") + return ordered_sections + + +def _ordered_entries( + section: str, entries: list[MarkdownFileEntry] +) -> list[MarkdownFileEntry]: + """Return entries in display order within a section.""" + if section == "MCP": + return sorted( + entries, + key=lambda entry: MCP_DOC_ORDER[entry.url_path.as_posix()], + ) + return entries + + +def generate_markdown_file_entries() -> tuple[MarkdownFileEntry, ...]: + """Generate the markdown files exposed to agents and llms.txt.""" + from reflex_docs.pages.docs import ( + all_docs, + doc_route_from_path, + doc_title_from_path, + manual_titles, + ) + from reflex_docs.whitelist import _check_whitelisted_path return tuple([ - (PosixPath(route.strip("/") + ".md"), resolved.read_bytes()) - for route, source_path in doc_markdown_sources.items() + MarkdownFileEntry( + url_path=Path(route.strip("/") + ".md"), + source_path=resolved, + title=manual_titles.get( + virtual_path, + _extract_markdown_title(resolved.read_text(encoding="utf-8")) + or _format_title(doc_title_from_path(virtual_path)), + ), + section=_section_for_path(Path(route.strip("/") + ".md")), + ) + for virtual_path, source_path in sorted(all_docs.items()) + if not virtual_path.endswith(("-style.md", "-ll.md")) + if _check_whitelisted_path(route := doc_route_from_path(virtual_path)) if (resolved := Path(source_path)).is_file() ]) +def generate_markdown_files() -> tuple[tuple[Path, str | bytes], ...]: + """Generate markdown asset contents for agent-friendly docs pages.""" + return tuple( + (entry.url_path, entry.source_path.read_bytes()) + for entry in generate_markdown_file_entries() + ) + + def generate_llms_txt( - markdown_files: Sequence[tuple[Path, str | bytes]], + markdown_files: Sequence[MarkdownFileEntry], ) -> tuple[Path, str]: - from reflex_base.config import get_config + """Generate an llms.txt index grouped by docs section.""" + sections: OrderedDict[str, list[MarkdownFileEntry]] = OrderedDict() + for markdown_file in markdown_files: + if not _include_in_llms_txt(markdown_file): + continue + sections.setdefault(markdown_file.section, []).append(markdown_file) - config = get_config() + lines = ["# Reflex", "", "## Docs", ""] + for section in _ordered_sections(sections): + entries = _ordered_entries(section, sections[section]) + lines.extend([f"### {section}", ""]) + lines.extend( + f"- [{entry.title}]({_llms_url_for_path(entry.url_path)})" + for entry in entries + ) + lines.append("") - if deploy_url := config.deploy_url: - deploy_url = config.deploy_url.removesuffix("/") - else: - deploy_url = "" + return (Path("llms.txt"), "\n".join(lines).rstrip() + "\n") - return ( - Path("docs") / "llms.txt", - "# Reflex\n\n" - + "## Docs\n\n" - + "\n".join( - f"- [{deploy_url}/{url}]({deploy_url}/{url})" for url, _ in markdown_files - ), - ) + +def generate_llms_full_txt( + markdown_files: Sequence[MarkdownFileEntry], +) -> tuple[Path, str]: + """Generate an llms-full.txt by stitching all docs markdown together.""" + lines = [ + LLMS_FULL_INTRO.format( + docs_home_url=_docs_home_url(), + llms_txt_url=_llms_url_for_path(Path("llms.txt")), + ).strip(), + "", + ] + for entry in markdown_files: + source = _strip_first_heading(entry.source_path.read_text(encoding="utf-8")) + lines.extend([ + f"# {entry.title}", + f"Source: {_llms_url_for_path(entry.url_path)}", + "", + source, + "", + ]) + + return (Path("llms-full.txt"), "\n".join(lines).rstrip() + "\n") def generate_agent_files() -> tuple[tuple[Path, str | bytes], ...]: - markdown_files = generate_markdown_files() - return (*markdown_files, generate_llms_txt(markdown_files)) + markdown_file_entries = generate_markdown_file_entries() + markdown_files = tuple( + (entry.url_path, entry.source_path.read_bytes()) + for entry in markdown_file_entries + ) + return ( + *markdown_files, + generate_llms_txt(markdown_file_entries), + generate_llms_full_txt(markdown_file_entries), + ) class AgentFilesPlugin(Plugin): diff --git a/docs/app/assets/llms.txt b/docs/app/assets/llms.txt deleted file mode 100644 index ea891d7ceff..00000000000 --- a/docs/app/assets/llms.txt +++ /dev/null @@ -1,386 +0,0 @@ -# Reflex - -> Reflex is the open-source framework empowering Python developers to build web apps faster. Build both your frontend and backend in a single language, Python (pip install reflex), with no JavaScript or web development experience required. - -# Things to remember when writing Reflex apps: - -## Reflex Basics - -This page gives an introduction to the most common concepts that you will use to build Reflex apps. - -# You will learn how to: -* Create and nest components -* Customize and style components -* Distinguish between compile-time and runtime -* Display data that changes over time -* Respond to events and update the screen -* Render conditions and lists -* Create pages and navigate between them -## Creating and nesting components - -[Components](https://reflex.dev/docs/ui/overview/) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components]({library.path}) to get you started quickly. - -Components are created using functions that return a component object. - -```python demo exec -def my_button(): - return rx.button("Click Me") -``` - -Components can be nested inside each other to create complex UIs. - -To nest components as children, pass them as positional arguments to the parent component. In the example below, the `rx.text` and `my_button` components are children of the `rx.box` component. - -```python demo exec -def my_page(): - return rx.box( - rx.text("This is a page"), - # Reference components defined in other functions. - my_button() - ) -``` - -You can also use any base HTML element through the [`rx.el`](https://reflex.dev/docs/library/other/html/) namespace. - -```python demo exec -def my_div(): - return rx.el.div( - rx.el.p("Use base html!"), - ) -``` - -If you need a component not provided by Reflex, you can check the [3rd party ecosystem]({custom_components.path}) or [wrap your own React component](https://reflex.dev/docs/wrapping-react/overview/). - - -## Customizing and styling components - -Components can be customized using [props](https://reflex.dev/docs/components/props/), which are passed in as keyword arguments to the component function. - -Each component has props that are specific to that component. Check the docs for the component you are using to see what props are available. - -```python demo exec -def half_filled_progress(): - return rx.progress(value=50) -``` - -In addition to component-specific props, components can also be styled using CSS properties passed as props. - -```python demo exec -def round_button(): - return rx.button("Click Me", border_radius="15px", font_size="18px") -``` - -```md alert -Use the `snake_case` version of the CSS property name as the prop name. -``` - -See the [styling guide](https://reflex.dev/docs/styling/overview/) for more information on how to style components - -In summary, components are made up of children and props. - -```md definition -# Children -* Text or other Reflex components nested inside a component. -* Passed as **positional arguments**. - -# Props -* Attributes that affect the behavior and appearance of a component. -* Passed as **keyword arguments**. -``` - -## Displaying data that changes over time - -Apps need to store and display data that changes over time. Reflex handles this through [State](https://reflex.dev/docs/state/overview/), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. - -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables [vars](https://reflex.dev/docs/vars/base-vars/) should have a type annotation, and can be initialized with a default value. - -```python -class MyState(rx.State): - count: int = 0 -``` - -### Referencing state vars in components - -To reference a state var in a component, you can pass it as a child or prop. The component will automatically update when the state changes. - -Vars are referenced through class attributes on your state class. For example, to reference the `count` var in a component, use `MyState.count`. - -```python demo exec -class MyState(rx.State): - count: int = 0 - color: str = "red" - -def counter(): - return rx.hstack( - # The heading `color` prop is set to the `color` var in MyState. - rx.heading("Count: ", color=MyState.color), - # The `count` var in `MyState` is passed as a child to the heading component. - rx.heading(MyState.count), - ) -``` - -Vars can be referenced in multiple components, and will automatically update when the state changes. - -## Responding to events and updating the screen - -So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers](https://reflex.dev/docs/events/events-overview/). - -```md alert -Event handlers are the ONLY way to change state in Reflex. -``` - -Components have special props called event triggers, such as `on_click`, called event triggers, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. - -```python demo exec -class CounterState(rx.State): - count: int = 0 - - def increment(self): - self.count += 1 - -def counter_increment(): - return rx.hstack( - rx.heading(CounterState.count), - rx.button("Increment", on_click=CounterState.increment) - ) -``` - -When an event trigger is activated, the event handler is called, which updates the state. The UI is automatically re-rendered to reflect the new state. - -### Event handlers with arguments - -Event handlers can also take in arguments. For example, the `increment` event handler can take an argument to increment the count by a specific amount. - -```python demo exec -class CounterState2(rx.State): - count: int = 0 - - def increment(self, amount: int): - self.count += amount - -def counter_variable(): - return rx.hstack( - rx.heading(CounterState2.count), - rx.button("Increment by 1", on_click=lambda: CounterState2.increment(1)), - rx.button("Increment by 5", on_click=lambda: CounterState2.increment(5)), - ) -``` - -The `on_click` event trigger doesn't pass any arguments here, but some event triggers do. For example, the `on_blur` event trigger passes the text of an input as an argument to the event handler. - -```python demo exec -class TextState(rx.State): - text: str = "" - - def update_text(self, new_text: str): - self.text = new_text - -def text_input(): - return rx.vstack( - rx.heading(TextState.text), - rx.input(default_value=TextState.text, on_blur=TextState.update_text), - ) -``` - -```md alert -Make sure that the event handler has the same number of arguments as the event trigger, or an error will be raised. -``` - -## Compile-time vs. runtime (IMPORTANT) - -Before we dive deeper into state, it's important to understand the difference between compile-time and runtime in Reflex. - -When you run your app, the frontend gets compiled to Javascript code that runs in the browser (compile-time). The backend stays in Python and runs on the server during the lifetime of the app (runtime). - -### When can you not use pure Python? - -We cannot compile arbitrary Python code, only the components that you define. What this means importantly is that you cannot use arbitrary Python operations and functions on state vars in components. - -However, since any event handlers in your state are on the backend, you **can use any Python code or library** within your state. - -### Examples that work - -Within an event handler, use any Python code or library. - -```python demo exec -def check_even(num: int): - return num % 2 == 0 - -class MyState3(rx.State): - count: int = 0 - text: str = "even" - - def increment(self): - # Use any Python code within state. - # Even reference functions defined outside the state. - if check_even(self.count): - self.text = "even" - else: - self.text = "odd" - self.count += 1 - -def count_and_check(): - return rx.box( - rx.heading(MyState3.text), - rx.button("Increment", on_click=MyState3.increment) - ) -``` - -Use any Python function within components, as long as it is defined at compile time (i.e. does not reference any state var) - -```python demo exec -def show_numbers(): - return rx.vstack( - *[ - rx.hstack(i, check_even(i)) - for i in range(10) - ] - ) -``` - -### Examples that don't work - -You cannot do an `if` statement on vars in components, since the value is not known at compile time. - -```python -class BadState(rx.State): - count: int = 0 - -def count_if_even(): - return rx.box( - rx.heading("Count: "), - # This will raise a compile error, as MyState.count is a var and not known a compile time. - rx.text(BadState.count if BadState.count % 2 == 0 else "Odd"), - # Using an if statement with a var as a prop will NOT work either. - rx.text("hello", color="red" if BadState.count % 2 == 0 else "blue"), - ) -``` - -You cannot do a `for` loop over a list of vars. - -```python -class BadState(rx.State): - items: list[str] = ["Apple", "Banana", "Cherry"] - -def loop_over_list(): - return rx.box( - # This will raise a compile error, as MyState.items is a list and not known at compile time. - *[rx.text(item) for item in BadState.items] - ) -``` - -You cannot do arbitrary Python operations on state vars in components. - -```python -class BadTextState(rx.State): - text: str = "Hello world" - -def format_text(): - return rx.box( - # Python operations such as `len` will not work on state vars. - rx.text(len(BadTextState.text)), - ) -``` - -In the next sections, we will show how to handle these cases. - -## Conditional rendering - -As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [`rx.cond`](https://reflex.dev/docs/components/conditional-rendering/) function to conditionally render components. - -```python demo exec -class LoginState(rx.State): - logged_in: bool = False - - def toggle_login(self): - self.logged_in = not self.logged_in - -def show_login(): - return rx.box( - rx.cond( - LoginState.logged_in, - rx.heading("Logged In"), - rx.heading("Not Logged In"), - ), - rx.button("Toggle Login", on_click=LoginState.toggle_login) - ) -``` - -## Rendering lists - -To iterate over a var that is a list, use the [`rx.foreach`](https://reflex.dev/docs/components/rendering-iterables/) function to render a list of components. - -Pass the list var and a function that returns a component as arguments to `rx.foreach`. - -```python demo exec -class ListState(rx.State): - items: list[str] = ["Apple", "Banana", "Cherry"] - -def render_item(item: rx.Var[str]): - # Note that item here is a Var, not a str! - return rx.list.item(item) - -def show_fruits(): - return rx.box( - rx.foreach(ListState.items, render_item), - ) -``` - -The function that renders each item takes in a `Var`, since this will get compiled up front. - -## Var Operations - -You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations](https://reflex.dev/docs/vars/var-operations/) that you can use to manipulate state vars. - -For example, to check if a var is even, you can use the `%` and `==` var operations. - -```python demo exec -class CountEvenState(rx.State): - count: int = 0 - - def increment(self): - self.count += 1 - -def count_if_even(): - return rx.box( - rx.heading("Count: "), - rx.cond( - # Here we use the `%` and `==` var operations to check if the count is even. - CountEvenState.count % 2 == 0, - rx.text("Even"), - rx.text("Odd"), - ), - rx.button("Increment", on_click=CountEvenState.increment), - ) -``` - -## App and Pages - -Reflex apps are created by instantiating the `rx.App` class. Pages are linked to specific URL routes, and are created by defining a function that returns a component. - -```python -def index(): - return rx.text('Root Page') - -rx.app = rx.App() -app.add_page(index, route="/") -``` - -## Next Steps - -Keep exploring the docs to start building your own Reflex app. - -## Docs - -Installation: [Installation](https://reflex.dev/docs/getting-started/installation/) -Introduction: [Getting Started](https://reflex.dev/docs/getting-started/introduction) -Reflex Basics: [Reflex Basics](https://reflex.dev/docs/getting-started/basics/) -Project Structure: [Project Structure](https://reflex.dev/docs/getting-started/project-structure/) -Full List of Components: [Component Library](https://reflex.dev/docs/library/) -Recipes: [Recipes](https://reflex.dev/docs/recipes/) - - -## Examples - -Dashboard Tutorial: [Dashboard Tutorial](https://reflex.dev/docs/getting-started/dashboard-tutorial/) -Chat App Tutorial: [Chat App Tutorial](https://reflex.dev/docs/getting-started/chatapp-tutorial/) diff --git a/docs/app/reflex_docs/components/docpage/navbar/buttons/sidebar.py b/docs/app/reflex_docs/components/docpage/navbar/buttons/sidebar.py index ca1bcb9ad6d..e3d70bf6405 100644 --- a/docs/app/reflex_docs/components/docpage/navbar/buttons/sidebar.py +++ b/docs/app/reflex_docs/components/docpage/navbar/buttons/sidebar.py @@ -62,7 +62,8 @@ def drawer_item(text: str, url: str, active_str: str = "") -> rx.Component: ~router_path.contains("hosting") & ~router_path.contains("library") & ~router_path.contains("gallery") - & ~router_path.contains("ai-builder"), + & ~router_path.startswith("/ai/") + & ~router_path.startswith("/docs/ai/"), ) if active_str == "": active = False diff --git a/docs/app/reflex_docs/pages/docs/__init__.py b/docs/app/reflex_docs/pages/docs/__init__.py index 1b0f46f33bf..0784414fe5a 100644 --- a/docs/app/reflex_docs/pages/docs/__init__.py +++ b/docs/app/reflex_docs/pages/docs/__init__.py @@ -161,6 +161,8 @@ def doc_route_from_path(doc: str) -> str: """ doc = doc.replace("\\", "/") doc = doc.removeprefix("docs/") + if doc.startswith("ai_builder/"): + doc = "ai/" + doc.removeprefix("ai_builder/") route = rx.utils.format.to_kebab_case(f"/{doc.replace('.md', '/')}") if route.endswith("/index/"): route = route[:-7] + "/" diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py index 788913d2e3f..2a9fb84e9a4 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py @@ -13,13 +13,14 @@ def get_integration_path() -> list: from integrations_docs import DOCS_DIR base_dir = str(DOCS_DIR) - web_path_prefix = "/ai-builder/integrations" + web_path_prefix = "/ai/integrations" result = [] exclude_files = [ "mcp_installation", "mcp_overview", "overview", + "skills", "snowflake", ] # without .md extension diff --git a/docs/app/reflex_docs/pages/integrations/integration_list.py b/docs/app/reflex_docs/pages/integrations/integration_list.py index f95c17403c4..128dfc7297b 100644 --- a/docs/app/reflex_docs/pages/integrations/integration_list.py +++ b/docs/app/reflex_docs/pages/integrations/integration_list.py @@ -8,13 +8,14 @@ def get_integration_path() -> list: from integrations_docs import DOCS_DIR base_dir = str(DOCS_DIR) - web_path_prefix = "/ai-builder/integrations" + web_path_prefix = "/ai/integrations" result = [] exclude_files = [ "mcp_installation", "mcp_overview", "overview", + "skills", "snowflake", ] # without .md extension diff --git a/docs/app/reflex_docs/reflex_docs.py b/docs/app/reflex_docs/reflex_docs.py index b891fe39518..5c75f0b29f0 100644 --- a/docs/app/reflex_docs/reflex_docs.py +++ b/docs/app/reflex_docs/reflex_docs.py @@ -92,8 +92,12 @@ # Call add_page with the dynamically constructed arguments app.add_page(**page_args) -# Add redirects -redirects = [] +# Add redirects. +redirects = [ + (route.path.replace("/ai/", "/ai-builder/", 1), route.path) + for route in routes + if route.path.startswith("/ai/") +] def _redirect_page(): diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 2779ae9920e..13ab5c26dab 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -210,7 +210,7 @@ def ask_ai_chat() -> rx.Component: class_name="justify-start pl-0 text-m-slate-7 dark:text-m-slate-6", native_button=False, ), - to="/ai-builder/integrations/mcp-overview/", + to="/ai/integrations/mcp-overview/", ) diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index 47b1dc48689..a41dfe03d0f 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -11,7 +11,9 @@ from .sidebar_items.ai import ( ai_builder_integrations, ai_builder_overview_items, + ai_onboarding_items, mcp_items, + skills_items, ) from .sidebar_items.component_lib import component_lib, graphing_libs from .sidebar_items.enterprise import ( @@ -280,6 +282,9 @@ def append_to_items(items, flat_items): + recipes + ai_builder_overview_items + ai_builder_integrations + + ai_onboarding_items + + mcp_items + + skills_items + api_reference + enterprise_items, flat_items, @@ -360,6 +365,7 @@ def create_sidebar_section( items: list[SideBarItem], index: rx.Var[list[str]] | list[str], url: rx.Var[str] | str, + connected_line: bool = False, ) -> rx.Component: # Check if the section has any nested sections (Like the Other Libraries Section) nested = any(len(child.children) > 0 for item in items for child in item.children) @@ -388,7 +394,10 @@ def create_sidebar_section( type="multiple", collapsible=True, default_value=index[:1].foreach(lambda x: "index" + x.to_string()), - class_name="ml-0 pl-0 w-full !bg-transparent !shadow-none rounded-[0px] flex flex-col gap-1", + class_name=ui.cn( + "ml-0 pl-0 w-full !bg-transparent !shadow-none rounded-[0px] flex flex-col", + "gap-0" if connected_line else "gap-1", + ), ), class_name="flex flex-col items-start ml-0 w-full", ) @@ -407,7 +416,9 @@ def sidebar_comp( recipes_index: list[int], enterprise_usage_index: list[int], enterprise_component_index: list[int], + ai_onboarding_index: list[int], mcp_index: list[int], + skills_index: list[int], # cli_ref_index: list[int], ai_builder_overview_index: list[int], @@ -427,9 +438,7 @@ def sidebar_comp( _is_docs_hosting = _path.startswith("/docs/hosting/") | _path.startswith( "/hosting/" ) - _is_docs_ai_builder = _path.startswith("/docs/ai-builder/") | _path.startswith( - "/ai-builder/" - ) + _is_docs_ai_builder = _path.startswith("/docs/ai/") | _path.startswith("/ai/") return rx.box( # pyright: ignore [reportCallIssue] # Handle sidebar categories for docs/cloud first @@ -454,8 +463,8 @@ def sidebar_comp( 0, ), sidebar_category( - "MCP", - ai_builder_pages.integrations.mcp_overview.path, + "MCP/Skills", + ai_builder_pages.integrations.ai_onboarding.path, "plug", 1, ), @@ -556,11 +565,27 @@ def sidebar_comp( 1, rx.el.ul( create_sidebar_section( - "MCP Integration", + "Overview", + ai_builder_pages.integrations.ai_onboarding.path, + ai_onboarding_items, + ai_onboarding_index, + url, + ), + create_sidebar_section( + "MCP", ai_builder_pages.integrations.mcp_overview.path, mcp_items, mcp_index, url, + connected_line=True, + ), + create_sidebar_section( + "Skills", + ai_builder_pages.integrations.skills.path, + skills_items, + skills_index, + url, + connected_line=True, ), class_name="flex flex-col items-start gap-8 w-full list-none list-style-none", ), @@ -720,7 +745,9 @@ def sidebar(url=None, width: str = "100%") -> rx.Component: cli_ref_index = calculate_index(cli_ref, url) ai_builder_overview_index = calculate_index(ai_builder_overview_items, url) ai_builder_integrations_index = calculate_index(ai_builder_integrations, url) + ai_onboarding_index = calculate_index(ai_onboarding_items, url) mcp_index = calculate_index(mcp_items, url) + skills_index = calculate_index(skills_items, url) return rx.box( sidebar_comp( @@ -735,10 +762,12 @@ def sidebar(url=None, width: str = "100%") -> rx.Component: recipes_index=recipes_index, enterprise_usage_index=enterprise_usage_index, enterprise_component_index=enterprise_component_index, + ai_onboarding_index=ai_onboarding_index, ai_builder_overview_index=ai_builder_overview_index, ai_builder_integrations_index=ai_builder_integrations_index, cli_ref_index=cli_ref_index, mcp_index=mcp_index, + skills_index=skills_index, width=width, ), on_mount=rx.call_script(Scrollable_SideBar), diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py index 3ccd1a4265d..b8cf7288a9b 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py @@ -1,3 +1,4 @@ +from ..state import SideBarItem from .item import create_item @@ -145,20 +146,45 @@ def get_ai_builder_integrations(): ] +def get_sidebar_items_ai_onboarding(): + from reflex_docs.pages.docs import ai_builder + + return [ + SideBarItem( + names="AI Onboarding", + link=ai_builder.integrations.ai_onboarding.path, + ), + ] + + def get_sidebar_items_mcp(): from reflex_docs.pages.docs import ai_builder return [ - create_item( - "MCP Integration", - children=[ - ai_builder.integrations.mcp_overview, - ai_builder.integrations.mcp_installation, - ], + SideBarItem( + names="Overview", + link=ai_builder.integrations.mcp_overview.path, + ), + SideBarItem( + names="Installation", + link=ai_builder.integrations.mcp_installation.path, + ), + ] + + +def get_sidebar_items_skills(): + from reflex_docs.pages.docs import ai_builder + + return [ + SideBarItem( + names="Overview", + link=ai_builder.integrations.skills.path, ), ] ai_builder_overview_items = get_sidebar_items_ai_builder_overview() ai_builder_integrations = get_ai_builder_integrations() +ai_onboarding_items = get_sidebar_items_ai_onboarding() mcp_items = get_sidebar_items_mcp() +skills_items = get_sidebar_items_skills() diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/state.py b/docs/app/reflex_docs/templates/docpage/sidebar/state.py index 74cda741c97..83e979c85aa 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/state.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/state.py @@ -48,6 +48,20 @@ def set_sidebar_index(self, num) -> int: @rx.var(initial_value=-1) def sidebar_index(self) -> int: route = self.router.url.path + if route.startswith(( + "/docs/ai/integrations/ai-onboarding", + "/ai/integrations/ai-onboarding", + )): + return 1 + if route.startswith(( + "/docs/ai/integrations/skills", + "/ai/integrations/skills", + )): + return 1 + if route.startswith(("/docs/ai/integrations/mcp", "/ai/integrations/mcp")): + return 1 + if route.startswith(("/docs/ai/", "/ai/")): + return 0 if self._sidebar_index < 0: if "library" in route: return 1 diff --git a/docs/app/reflex_docs/views/docs_navbar.py b/docs/app/reflex_docs/views/docs_navbar.py index 03f3180bc43..13d92039ba4 100644 --- a/docs/app/reflex_docs/views/docs_navbar.py +++ b/docs/app/reflex_docs/views/docs_navbar.py @@ -70,7 +70,7 @@ def menu_item(text: str, href: str, active_str: str = "") -> rx.Component: # For paths starting with "/" (like Start), use exact match # For "framework", it's the default - active when in /docs but not matching other sections - # For other segments (like "ai-builder"), use contains + # For other segments (like "ai"), use contains if active_str.startswith("/"): if active_str == "/": active = (router_path == "/") | (router_path == "/index") @@ -78,9 +78,13 @@ def menu_item(text: str, href: str, active_str: str = "") -> rx.Component: active = router_path == active_str elif active_str == "framework": is_overview = (router_path == "/") | (router_path == "/index") - is_ai_builder = router_path.contains("ai-builder") + is_ai_builder = router_path.startswith("/ai/") | router_path.startswith( + "/docs/ai/" + ) is_hosting = router_path.contains("hosting") active = ~is_overview & ~is_ai_builder & ~is_hosting + elif active_str == "ai": + active = router_path.startswith("/ai/") | router_path.startswith("/docs/ai/") else: active = router_path.contains(active_str) @@ -106,9 +110,7 @@ def navigation_menu() -> rx.Component: return ui.navigation_menu.root( ui.navigation_menu.list( menu_item("Overview", "/", "/"), - menu_item( - "Build with AI", ai_builder.overview.best_practices.path, "ai-builder" - ), + menu_item("Build with AI", ai_builder.overview.best_practices.path, "ai"), menu_item("Framework", getting_started.introduction.path, "framework"), menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"), class_name="flex flex-row items-center gap-2 m-0 h-full list-none", diff --git a/docs/app/tests/test_agent_files.py b/docs/app/tests/test_agent_files.py new file mode 100644 index 00000000000..3a411b7a11c --- /dev/null +++ b/docs/app/tests/test_agent_files.py @@ -0,0 +1,176 @@ +"""Tests for agent-facing static file generation.""" + +from pathlib import Path +from types import SimpleNamespace + +from agent_files._plugin import ( + MarkdownFileEntry, + generate_llms_full_txt, + generate_llms_txt, +) + + +def test_generate_llms_txt_groups_docs_at_public_root(monkeypatch): + """The docs mount exposes public-root llms.txt as /docs/llms.txt.""" + monkeypatch.setattr( + "reflex_base.config.get_config", + lambda: SimpleNamespace( + deploy_url="https://reflex.dev", + frontend_path="/docs", + ), + ) + + path, content = generate_llms_txt([ + MarkdownFileEntry( + url_path=Path("components/props.md"), + source_path=Path("docs/components/props.md"), + title="Props", + section="Components", + ), + MarkdownFileEntry( + url_path=Path("components/rendering-iterables.md"), + source_path=Path("docs/components/rendering_iterables.md"), + title="Rendering Iterables", + section="Components", + ), + MarkdownFileEntry( + url_path=Path("state/overview.md"), + source_path=Path("docs/state/overview.md"), + title="State Overview", + section="State", + ), + MarkdownFileEntry( + url_path=Path("ai/overview/best-practices.md"), + source_path=Path("docs/ai_builder/overview/best_practices.md"), + title="Reflex Build: Best Practices", + section="AI Builder", + ), + MarkdownFileEntry( + url_path=Path("ai/integrations/resend.md"), + source_path=Path("docs/ai_builder/integrations/resend.md"), + title="Resend Integration", + section="AI Builder", + ), + MarkdownFileEntry( + url_path=Path("ai/integrations/mcp-overview.md"), + source_path=Path("docs/ai_builder/integrations/mcp_overview.md"), + title="Overview", + section="MCP", + ), + MarkdownFileEntry( + url_path=Path("ai/integrations/ai-onboarding.md"), + source_path=Path("docs/ai_builder/integrations/ai_onboarding.md"), + title="AI Onboarding", + section="AI Onboarding", + ), + MarkdownFileEntry( + url_path=Path("ai/integrations/mcp-installation.md"), + source_path=Path("docs/ai_builder/integrations/mcp_installation.md"), + title="Installation", + section="MCP", + ), + MarkdownFileEntry( + url_path=Path("ai/integrations/skills.md"), + source_path=Path("docs/ai_builder/integrations/skills.md"), + title="Skills", + section="Skills", + ), + MarkdownFileEntry( + url_path=Path("ai/features/ide.md"), + source_path=Path("docs/ai_builder/features/ide.md"), + title="Reflex Build IDE", + section="AI Builder", + ), + ]) + + assert path == Path("llms.txt") + assert content.startswith("# Reflex\n\n## Docs\n\n") + assert "### Components\n\n" in content + assert "- [Props](https://reflex.dev/docs/components/props.md)" in content + assert ( + "- [Rendering Iterables](https://reflex.dev/docs/components/rendering-iterables.md)" + in content + ) + assert "### State\n\n" in content + assert "- [State Overview](https://reflex.dev/docs/state/overview.md)" in content + assert "### AI Builder\n\n" in content + assert ( + "- [Reflex Build: Best Practices](https://reflex.dev/docs/ai/overview/best-practices.md)" + in content + ) + assert "Resend Integration" not in content + assert "Reflex Build IDE" not in content + assert "### AI Onboarding\n\n" in content + assert ( + "- [AI Onboarding](https://reflex.dev/docs/ai/integrations/ai-onboarding.md)" + in content + ) + assert "### MCP\n\n" in content + assert ( + "- [Overview](https://reflex.dev/docs/ai/integrations/mcp-overview.md)" + in content + ) + assert ( + "- [Installation](https://reflex.dev/docs/ai/integrations/mcp-installation.md)" + in content + ) + assert "### Skills\n\n" in content + assert "- [Skills](https://reflex.dev/docs/ai/integrations/skills.md)" in content + assert content.index("### AI Builder") < content.index("### AI Onboarding") + assert content.index("### AI Onboarding") < content.index("### MCP") + assert content.index("### MCP") < content.index("### Skills") + assert content.index("mcp-overview.md") < content.index("mcp-installation.md") + + +def test_generate_llms_full_txt_stitches_markdown_docs(monkeypatch, tmp_path): + """llms-full.txt contains full Markdown page bodies with source URLs.""" + monkeypatch.setattr( + "reflex_base.config.get_config", + lambda: SimpleNamespace( + deploy_url="https://reflex.dev", + frontend_path="/docs", + ), + ) + introduction = tmp_path / "introduction.md" + introduction.write_text( + "# Introduction\n\nBuild full-stack apps in Python.\n", + encoding="utf-8", + ) + props = tmp_path / "props.md" + props.write_text( + "# Props\n\nUse props to configure components.\n", + encoding="utf-8", + ) + + path, content = generate_llms_full_txt([ + MarkdownFileEntry( + url_path=Path("getting-started/introduction.md"), + source_path=introduction, + title="Introduction", + section="Getting Started", + ), + MarkdownFileEntry( + url_path=Path("components/props.md"), + source_path=props, + title="Props", + section="Components", + ), + ]) + + assert path == Path("llms-full.txt") + assert content.startswith( + "# Reflex Documentation\n" + "Source: https://reflex.dev/docs/\n\n" + "This file stitches together the full Reflex documentation as Markdown" + ) + assert "[llms.txt](https://reflex.dev/docs/llms.txt)" in content + assert ( + "# Introduction\n" + "Source: https://reflex.dev/docs/getting-started/introduction.md\n\n" + "Build full-stack apps in Python." + ) in content + assert ( + "# Props\n" + "Source: https://reflex.dev/docs/components/props.md\n\n" + "Use props to configure components." + ) in content diff --git a/docs/app/tests/test_routes.py b/docs/app/tests/test_routes.py index 64be3dea240..a75eff34751 100644 --- a/docs/app/tests/test_routes.py +++ b/docs/app/tests/test_routes.py @@ -30,3 +30,16 @@ def test_unique_routes(routes_fixture): assert len(duplicates) == 0, f"Duplicate routes found: {duplicates}" print(f"Test passed. All {len(paths)} routes are unique.") + + +def test_ai_builder_routes_use_ai_prefix(routes_fixture): + paths = {route.path for route in routes_fixture if route.path} + + assert "/ai/overview/best-practices/" in paths + assert "/ai/integrations/ai-onboarding/" in paths + assert "/ai/integrations/mcp-overview/" in paths + assert "/ai/integrations/skills/" in paths + assert "/ai-builder/overview/best-practices/" not in paths + assert "/ai-builder/integrations/ai-onboarding/" not in paths + assert "/ai-builder/integrations/mcp-overview/" not in paths + assert "/ai-builder/integrations/skills/" not in paths diff --git a/docs/authentication/authentication_overview.md b/docs/authentication/authentication_overview.md index 68d97ce78b5..2ad354c2bb0 100644 --- a/docs/authentication/authentication_overview.md +++ b/docs/authentication/authentication_overview.md @@ -12,7 +12,7 @@ We have solutions that currently exist outside of the core framework: 5. Clerk Auth: A community member wrapped this component and hooked it up in this app: https://github.com/TimChild/reflex-clerk-api 6. Descope Auth: Enables authentication with Descope, supporting passwordless, social login, SSO, and MFA: https://github.com/descope-sample-apps/reflex-descope-auth -If you're using the AI Builder, you can also use the built-in [Authentication Integrations](/docs/ai-builder/integrations/overview) which include Azure Auth, Google Auth, Okta Auth, and Descope. +If you're using the AI Builder, you can also use the built-in [Authentication Integrations](/docs/ai/integrations/overview) which include Azure Auth, Google Auth, Okta Auth, and Descope. ## Guidance for Implementing Authentication diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 98db68866b0..383a57e0dd4 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -119,11 +119,11 @@ tabs() ```python demo box rx.box( - rx._x.code_block( + rx.code_block( """import reflex as rx """, class_name="code-block !bg-transparent !border-none", ), - rx._x.code_block( + rx.code_block( """class State(rx.State): count: int = 0 @@ -146,7 +146,7 @@ rx.box( ), class_name="code-block", ), - rx._x.code_block( + rx.code_block( """def index(): return rx.hstack( rx.button( @@ -174,7 +174,7 @@ rx.box( ), class_name="code-block", ), - rx._x.code_block( + rx.code_block( """app = rx.App() app.add_page(index)""", background=rx.cond( diff --git a/docs/library/forms/form-ll.md b/docs/library/forms/form-ll.md index 61782d3a725..56347f14646 100644 --- a/docs/library/forms/form-ll.md +++ b/docs/library/forms/form-ll.md @@ -143,7 +143,7 @@ In this example, the `rx.input` has an attribute `type="email"` and the `form.me ## Form Anatomy ```python eval -rx._x.code_block( +rx.code_block( """form.root( form.field( form.label(...), diff --git a/docs/recipes/auth/login_form.md b/docs/recipes/auth/login_form.md index 9541456b16e..6b1e4f33c8b 100644 --- a/docs/recipes/auth/login_form.md +++ b/docs/recipes/auth/login_form.md @@ -232,8 +232,8 @@ def login_single_thirdparty() -> rx.Component: width="100%", ), rx.button( - rx.icon(tag="github"), - "Sign in with Github", + rx.text("GH", weight="bold"), + "Sign in with GitHub", variant="outline", size="3", width="100%", @@ -318,9 +318,9 @@ def login_multiple_thirdparty() -> rx.Component: width="100%", ), rx.center( - rx.icon_button(rx.icon(tag="github"), variant="soft", size="3"), - rx.icon_button(rx.icon(tag="facebook"), variant="soft", size="3"), - rx.icon_button(rx.icon(tag="twitter"), variant="soft", size="3"), + rx.icon_button(rx.text("GH", weight="bold"), variant="soft", size="3"), + rx.icon_button(rx.text("f", weight="bold"), variant="soft", size="3"), + rx.icon_button(rx.text("X", weight="bold"), variant="soft", size="3"), spacing="4", direction="row", width="100%", diff --git a/docs/recipes/auth/signup_form.md b/docs/recipes/auth/signup_form.md index 1de5789431f..1bf1e44c406 100644 --- a/docs/recipes/auth/signup_form.md +++ b/docs/recipes/auth/signup_form.md @@ -257,8 +257,8 @@ def signup_single_thirdparty() -> rx.Component: width="100%", ), rx.button( - rx.icon(tag="github"), - "Sign in with Github", + rx.text("GH", weight="bold"), + "Sign in with GitHub", variant="outline", size="3", width="100%", @@ -351,9 +351,9 @@ def signup_multiple_thirdparty() -> rx.Component: width="100%", ), rx.center( - rx.icon_button(rx.icon(tag="github"), variant="soft", size="3"), - rx.icon_button(rx.icon(tag="facebook"), variant="soft", size="3"), - rx.icon_button(rx.icon(tag="twitter"), variant="soft", size="3"), + rx.icon_button(rx.text("GH", weight="bold"), variant="soft", size="3"), + rx.icon_button(rx.text("f", weight="bold"), variant="soft", size="3"), + rx.icon_button(rx.text("X", weight="bold"), variant="soft", size="3"), spacing="4", direction="row", width="100%", diff --git a/docs/recipes/layout/footer.md b/docs/recipes/layout/footer.md index 11ea0adf9af..04708a3feef 100644 --- a/docs/recipes/layout/footer.md +++ b/docs/recipes/layout/footer.md @@ -41,16 +41,16 @@ def footer_items_2() -> rx.Component: ) -def social_link(icon: str, href: str) -> rx.Component: - return rx.link(rx.icon(icon), href=href) +def social_link(label: str, href: str) -> rx.Component: + return rx.link(rx.text(label, weight="bold"), href=href) def socials() -> rx.Component: return rx.flex( - social_link("instagram", "/#"), - social_link("twitter", "/#"), - social_link("facebook", "/#"), - social_link("linkedin", "/#"), + social_link("IG", "/#"), + social_link("X", "/#"), + social_link("f", "/#"), + social_link("in", "/#"), spacing="3", justify="end", width="100%", @@ -143,16 +143,16 @@ def footer_items_2() -> rx.Component: ) -def social_link(icon: str, href: str) -> rx.Component: - return rx.link(rx.icon(icon), href=href) +def social_link(label: str, href: str) -> rx.Component: + return rx.link(rx.text(label, weight="bold"), href=href) def socials() -> rx.Component: return rx.flex( - social_link("instagram", "/#"), - social_link("twitter", "/#"), - social_link("facebook", "/#"), - social_link("linkedin", "/#"), + social_link("IG", "/#"), + social_link("X", "/#"), + social_link("f", "/#"), + social_link("in", "/#"), spacing="3", justify_content=["center", "center", "end"], width="100%", @@ -268,16 +268,16 @@ def footer_items_3() -> rx.Component: ) -def social_link(icon: str, href: str) -> rx.Component: - return rx.link(rx.icon(icon), href=href) +def social_link(label: str, href: str) -> rx.Component: + return rx.link(rx.text(label, weight="bold"), href=href) def socials() -> rx.Component: return rx.flex( - social_link("instagram", "/#"), - social_link("twitter", "/#"), - social_link("facebook", "/#"), - social_link("linkedin", "/#"), + social_link("IG", "/#"), + social_link("X", "/#"), + social_link("f", "/#"), + social_link("in", "/#"), spacing="3", justify_content=["center", "center", "end"], width="100%", diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md index 452197fe85d..5f93c6157ff 100644 --- a/docs/wrapping-react/overview.md +++ b/docs/wrapping-react/overview.md @@ -58,6 +58,7 @@ We also have a var `color` which is the current color of the color picker. Since this component has interaction we must specify any event triggers that the component takes. The color picker has a single trigger `on_change` to specify when the color changes. This trigger takes in a single argument `color` which is the new color. ```python exec +from reflex.experimental.client_state import ClientStateVar from reflex.components.component import NoSSRComponent @@ -70,7 +71,7 @@ class ColorPicker(NoSSRComponent): color_picker = ColorPicker.create -ColorPickerState = rx._x.client_state(default="#db114b", var_name="color") +ColorPickerState = ClientStateVar.create(default="#db114b", var_name="color") ``` ```python eval diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py index 213909e6b0f..a2557b46da7 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py @@ -1,5 +1,7 @@ """Code block components for documentation pages.""" +from reflex_components_code.shiki_code_block import code_block as shiki_code_block + import reflex as rx import reflex_site_shared.styles.fonts as fonts from reflex_site_shared import styles @@ -16,7 +18,7 @@ def _plain_code_block(code: str, language: str): The component. """ return rx.box( - rx._x.code_block( + shiki_code_block( code, language=language, class_name="code-block", @@ -88,7 +90,7 @@ def code_block_dark(code: str, language: str): The component. """ return rx.box( - rx._x.code_block( + shiki_code_block( code, language=language, class_name="code-block", @@ -132,7 +134,7 @@ def doccmdoutput( The styled command and its example output. """ return rx.vstack( - rx._x.code_block( + shiki_code_block( command, can_copy=True, border_radius=styles.DOC_BORDER_RADIUS, @@ -148,7 +150,7 @@ def doccmdoutput( font_family="JetBrains Mono", width="100%", ), - rx._x.code_block( + shiki_code_block( output, can_copy=False, border_radius="12px", diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py index ba735dc6628..5b52091bf83 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py @@ -2,6 +2,7 @@ # pyright: reportAttributeAccessIssue=false from reflex_base.constants.colors import ColorType +from reflex_components_code.shiki_code_block import code_block as shiki_code_block import reflex as rx from reflex_site_shared.components.blocks.code import ( @@ -139,12 +140,12 @@ def markdown_codeblock(value: str, **props: object) -> rx.Component: Returns: The component. """ - return rx._x.code_block(value, **props) + return shiki_code_block(value, **props) def markdown_with_shiki(*args, **kwargs): """Wrapper for the markdown component with a customized component map. - Uses the experimental Shiki-based code block (rx._x.code_block) + Uses the Shiki-based code block instead of the default CodeBlock component for code blocks. Note: This wrapper should be removed once the default codeblock