from __future__ import annotations import reflex as rx from .report_formatting import MARKDOWN_CSS from .state import State PAGE_BG = "linear-gradient(135deg, #fbfdff 0%, #fffdf7 48%, #f8fff9 100%)" PAGE_PADDING = {"initial": "1rem", "sm": "1.25rem", "lg": "2rem"} PANEL_PADDING = {"initial": "1rem", "sm": "1.25rem"} GUIDE_URL = "https://machinelearningmastery.com/how-to-build-a-multi-agent-research-assistant-in-python/" OPENAI_API_KEYS_URL = "https://platform.openai.com/api-keys" OLOSTEP_AUTH_URL = "https://olostep.com/auth" def status_badge() -> rx.Component: return rx.badge( State.status, color_scheme=rx.cond( State.status == "Complete", "green", rx.cond(State.status == "Failed", "red", "blue"), ), variant="soft", size="2", ) def log_panel() -> rx.Component: return rx.box( rx.hstack( rx.hstack( rx.icon("activity", size=18, color="#0b7285"), rx.heading("Working", size="3", color="#14323b"), align="center", spacing="2", ), status_badge(), justify="between", align="center", width="100%", ), rx.vstack( rx.foreach( State.logs, lambda item: rx.text( item, font_family="monospace", font_size="0.8rem", color="#263238", ), ), align="stretch", spacing="2", margin_top="0.6rem", ), max_height="10rem", overflow_y="auto", padding="0.85rem", border="1px solid #b6e3ea", border_radius="8px", background="rgba(235, 251, 255, 0.9)", box_shadow="0 8px 24px rgba(8, 92, 115, 0.10)", width="100%", ) def report_panel() -> rx.Component: return rx.box( rx.hstack( rx.spacer(), rx.button( rx.icon("download", size=17), "Download PDF", on_click=State.download_pdf, background="#2f9e44", color="white", border_radius="8px", padding_x="1rem", padding_y="0.45rem", cursor="pointer", margin_top="1rem", margin_right="1rem", _hover={ "background": "#238b36", "transform": "translateY(-1px)", "box_shadow": "0 4px 12px rgba(47, 158, 68, 0.35)", }, transition="all 0.2s ease", ), justify="end", align="start", width="100%", ), rx.html( MARKDOWN_CSS + '
' + State.report_html + "
", width="100%", overflow_x="auto", ), padding=PANEL_PADDING, border="1px solid #dde6ec", border_radius="8px", background="rgba(255, 255, 255, 0.96)", box_shadow="0 16px 40px rgba(36, 48, 58, 0.10)", width="100%", overflow_x="auto", ) def search_bar() -> rx.Component: return rx.hstack( rx.input( value=State.query, on_change=State.set_query, on_key_down=State.handle_key_down, placeholder="Ask anything", height="2.8rem", width="100%", flex="1 1 auto", min_width="0", background="transparent", border="0", box_shadow="none", font_size="1.05rem", margin_left="1.5rem", padding_left="0", text_indent="0.85rem", padding_right={"initial": "0.75rem", "sm": "1rem"}, ), rx.button( rx.icon("rotate_ccw", size=19), on_click=State.clear_all, aria_label="Reset", height="3.15rem", width="3.15rem", min_width="3.15rem", padding="0", flex="0 0 auto", border_radius="8px", background="transparent", color="#111111", box_shadow="none", cursor="pointer", _hover={"background": "#f3f4f6"}, ), rx.cond( State.is_running, rx.button( rx.icon("square", size=16), on_click=State.stop_report, aria_label="Stop", height="3.15rem", width="3.15rem", min_width="3.15rem", padding="0", flex="0 0 auto", border_radius="999px", background="#111111", color="white", cursor="pointer", position="relative", z_index="1", margin_right="1rem", ), rx.button( rx.icon("search", size=17), on_click=State.run_report, aria_label="Search", height="3.15rem", width="3.15rem", min_width="3.15rem", padding="0", flex="0 0 auto", border_radius="999px", background="#111111", color="white", cursor="pointer", position="relative", z_index="1", margin_right="1rem", ), ), width="100%", min_height="5rem", align="center", justify="between", spacing={"initial": "3", "sm": "4"}, align_self="center", padding={ "initial": "0.75rem 1.85rem 0.75rem 1.2rem", "sm": "0.8rem 2.35rem 0.8rem 1.6rem", }, border="1px solid #d8d8d8", border_radius="999px", background="rgba(255, 255, 255, 0.96)", box_shadow="0 18px 55px rgba(16, 24, 40, 0.12)", ) def prompt_suggestions() -> rx.Component: return rx.hstack( rx.button( "Is remote work dying in 2026?", on_click=State.set_query("Is remote work dying in 2026?"), variant="soft", color_scheme="gray", border_radius="999px", ), rx.button( "What's behind the global coffee shortage?", on_click=State.set_query("What's behind the global coffee shortage?"), variant="soft", color_scheme="gray", border_radius="999px", ), rx.button( "Are electric cars actually cheaper to own?", on_click=State.set_query("Are electric cars actually cheaper to own?"), variant="soft", color_scheme="gray", border_radius="999px", ), justify="center", align="center", wrap="wrap", spacing="3", width="100%", ) def page_header() -> rx.Component: return rx.vstack( rx.heading( "What do you want to research today?", size={"initial": "6", "md": "7"}, weight="regular", color="#101828", text_align="center", line_height="1.15", ), rx.hstack( rx.badge("Manager", color_scheme="blue", variant="soft", size="2", border_radius="999px"), rx.badge("Judge", color_scheme="orange", variant="soft", size="2", border_radius="999px"), rx.badge("Researcher", color_scheme="purple", variant="soft", size="2", border_radius="999px"), rx.badge("Analyst", color_scheme="green", variant="soft", size="2", border_radius="999px"), justify="center", align="center", wrap="wrap", spacing="2", ), width="100%", align="center", spacing="3", ) def result_area() -> rx.Component: return rx.cond( State.is_running, log_panel(), rx.cond( State.report_markdown != "", report_panel(), rx.box( rx.text("Enter a question and press Search.", color="#52616b", align="center"), padding="0.25rem", width="100%", ), ), ) def setup_dialog() -> rx.Component: return rx.dialog.root( rx.dialog.content( rx.vstack( rx.hstack( rx.box( rx.icon("key_round", size=22, color="#0b7285"), display="flex", align_items="center", justify_content="center", width="2.75rem", height="2.75rem", border_radius="8px", background="#e7f7fa", flex="0 0 auto", ), rx.vstack( rx.dialog.title( "Add your own API keys", color="#101828", font_size="1.25rem", line_height="1.2", margin="0", ), rx.dialog.description( "This Space cannot run research until it has your OpenAI and Olostep keys.", color="#52616b", line_height="1.5", margin="0", ), align="stretch", spacing="1", ), align="start", spacing="3", width="100%", ), rx.box( rx.text("Missing environment variables", color="#475467", size="2"), rx.text( State.missing_env_text, color="#101828", font_family="monospace", font_size="0.9rem", margin_top="0.25rem", ), padding="0.85rem", border="1px solid #dde6ec", border_radius="8px", background="#f8fafc", width="100%", ), rx.vstack( rx.hstack(rx.icon("copy", size=16), rx.text("Duplicate this app."), align="center", spacing="2"), rx.hstack(rx.icon("settings", size=16), rx.text("Open Settings > Variables and secrets."), align="center", spacing="2"), rx.hstack( rx.icon("plus", size=16), rx.text("Add OPENAI_API_KEY and OLOSTEP_API_KEY, then restart the app."), align="center", spacing="2", ), align="stretch", spacing="2", color="#344054", font_size="0.95rem", width="100%", ), rx.hstack( rx.link( rx.icon("book_open", size=16), "Full guide", href=GUIDE_URL, is_external=True, color="#0b7285", display="flex", align_items="center", gap="0.45rem", font_weight="600", ), rx.link( rx.icon("key_round", size=16), "OpenAI keys", href=OPENAI_API_KEYS_URL, is_external=True, color="#0b7285", display="flex", align_items="center", gap="0.45rem", font_weight="600", ), rx.link( rx.icon("lock_keyhole", size=16), "Olostep auth", href=OLOSTEP_AUTH_URL, is_external=True, color="#0b7285", display="flex", align_items="center", gap="0.45rem", font_weight="600", ), rx.spacer(), rx.button( "Close", on_click=State.close_setup_dialog, background="#111111", color="white", border_radius="8px", padding_x="1rem", cursor="pointer", ), width="100%", align="center", wrap="wrap", spacing="3", ), align="stretch", spacing="4", ), max_width="30rem", padding="1.25rem", border_radius="8px", box_shadow="0 24px 70px rgba(16, 24, 40, 0.24)", ), open=State.show_setup_dialog, on_open_change=State.set_setup_dialog_open, ) def index() -> rx.Component: return rx.box( setup_dialog(), rx.vstack( page_header(), search_bar(), prompt_suggestions(), rx.cond( State.error != "", rx.callout(State.error, icon="triangle_alert", color_scheme="red", width="100%"), ), result_area(), spacing="5", align="stretch", padding=PANEL_PADDING, width="100%", max_width="780px", margin_x="auto", ), width="100%", max_width="100vw", padding=PAGE_PADDING, min_height="100vh", background=PAGE_BG, overflow_x="hidden", display="flex", align_items="center", justify_content="center", ) app = rx.App() app.add_page(index, route="/", title="Multi-Agent Research Assistant")