mesop / main.py
wwwillchen's picture
Commit
45df88a
# Disable import sort ordering due to the hack needed
# to ensure local imports.
# ruff: noqa: E402
import base64
import inspect
import os
import sys
from dataclasses import dataclass
from typing import Literal
import mesop as me
# Append the current directory to sys.path to ensure local imports work
# This is required so mesop/examples/__init__.py can import the modules
# imported below.
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path:
sys.path.append(current_dir)
import glob
import audio as audio
import autocomplete as autocomplete
import badge as badge
import basic_animation as basic_animation
import bootstrap as bootstrap
import box as box
import button as button
import button_toggle as button_toggle
import card as card
import chat as chat
import chat_inputs as chat_inputs
import checkbox as checkbox
import code_demo as code_demo # cannot call it code due to python library naming conflict
import date_picker as date_picker
import date_range_picker as date_range_picker
import density as density
import dialog as dialog
import divider as divider
import embed as embed
import expansion_panel as expansion_panel
import fancy_chat as fancy_chat
import feedback as feedback
import form_billing as form_billing
import form_profile as form_profile
import grid_table as grid_table
import headers as headers
import html_demo as html_demo
import icon as icon
import image as image
import input as input
import link as link
import llm_playground as llm_playground
import llm_rewriter as llm_rewriter
import markdown_demo as markdown_demo # cannot call it markdown due to python library naming conflict
import markdown_editor as markdown_editor
import plot as plot
import progress_bar as progress_bar
import progress_spinner as progress_spinner
import radio as radio
import select_demo as select_demo # cannot call it select due to python library naming conflict
import sidenav as sidenav
import slide_toggle as slide_toggle
import slider as slider
import snackbar as snackbar
import table as table
import tailwind as tailwind
import text as text
import text_to_image as text_to_image
import text_to_text as text_to_text
import textarea as textarea
import tooltip as tooltip
import uploader as uploader
import video as video
@dataclass
class Example:
# module_name (should also be the path name)
name: str
@dataclass
class Section:
name: str
examples: list[Example]
FIRST_SECTIONS = [
Section(
name="Quick start",
examples=[
Example(name="chat"),
Example(name="text_to_image"),
Example(name="text_to_text"),
],
),
Section(
name="Use cases",
examples=[
Example(name="fancy_chat"),
Example(name="llm_rewriter"),
Example(name="llm_playground"),
Example(name="markdown_editor"),
],
),
Section(
name="Patterns",
examples=[
Example(name="dialog"),
Example(name="grid_table"),
Example(name="headers"),
Example(name="snackbar"),
Example(name="chat_inputs"),
Example(name="form_billing"),
Example(name="form_profile"),
],
),
Section(
name="Features",
examples=[
Example(name="density"),
],
),
Section(
name="Misc",
examples=[
Example(name="basic_animation"),
Example(name="feedback"),
],
),
Section(
name="Integrations",
examples=[
Example(name="bootstrap"),
Example(name="tailwind"),
],
),
]
COMPONENTS_SECTIONS = [
Section(
name="Layout",
examples=[
Example(name="box"),
Example(name="sidenav"),
],
),
Section(
name="Text",
examples=[
Example(name="text"),
Example(name="markdown_demo"),
Example(name="code_demo"),
],
),
Section(
name="Media",
examples=[
Example(name="image"),
Example(name="audio"),
Example(name="video"),
],
),
Section(
name="Form",
examples=[
Example(name="autocomplete"),
Example(name="button"),
Example(name="button_toggle"),
Example(name="checkbox"),
Example(name="date_picker"),
Example(name="date_range_picker"),
Example(name="input"),
Example(name="textarea"),
Example(name="radio"),
Example(name="select_demo"),
Example(name="slide_toggle"),
Example(name="slider"),
Example(name="uploader"),
],
),
Section(
name="Visual",
examples=[
Example(name="badge"),
Example(name="card"),
Example(name="divider"),
Example(name="expansion_panel"),
Example(name="icon"),
Example(name="progress_bar"),
Example(name="progress_spinner"),
Example(name="table"),
Example(name="tooltip"),
],
),
Section(
name="Web",
examples=[
Example(name="embed"),
Example(name="html_demo"),
Example(name="link"),
],
),
Section(
name="Others",
examples=[
Example(name="plot"),
],
),
]
ALL_SECTIONS = FIRST_SECTIONS + COMPONENTS_SECTIONS
BORDER_SIDE = me.BorderSide(
style="solid",
width=1,
color="#dcdcdc",
)
@me.stateclass
class State:
current_demo: str
panel_fullscreen: Literal["preview", "editor", None] = None
screenshots: dict[str, str] = {}
def load_home_page(e: me.LoadEvent):
if me.state(ThemeState).dark_mode:
me.set_theme_mode("dark")
else:
me.set_theme_mode("system")
yield
screenshot_dir = os.path.join(current_dir, "screenshots")
screenshot_files = glob.glob(os.path.join(screenshot_dir, "*.webp"))
for screenshot_file in screenshot_files:
image_name = os.path.basename(screenshot_file).split(".")[0]
with open(screenshot_file, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode()
screenshots[image_name] = "data:image/webp;base64," + encoded_string
yield
@me.page(
title="Mesop Demos",
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io", "https://huggingface.co"]
),
on_load=load_home_page,
)
def main_page():
header()
with me.box(
style=me.Style(
background=me.theme_var("background"),
flex_grow=1,
display="flex",
)
):
if is_desktop():
side_menu()
with me.box(
style=me.Style(
width="calc(100% - 150px)" if is_desktop() else "100%",
display="flex",
gap=24,
flex_direction="column",
padding=me.Padding.all(24),
overflow_y="auto",
)
):
with me.box(
style=me.Style(
height="calc(100vh - 120px)",
)
):
for section in ALL_SECTIONS:
with me.box(style=me.Style(margin=me.Margin(bottom=28))):
me.text(
section.name,
style=me.Style(
font_weight=500,
font_size=20,
margin=me.Margin(
bottom=16,
),
),
)
with me.box(
style=me.Style(
display="flex",
flex_direction="row",
flex_wrap="wrap",
gap=28,
)
):
for example in section.examples:
example_card(example.name)
def navigate_example_card(e: me.ClickEvent):
me.navigate("/embed/" + e.key)
def example_card(name: str):
with me.box(
key=name,
on_click=navigate_example_card,
style=me.Style(
border=me.Border.all(
me.BorderSide(
width=1,
color="rgb(220, 220, 220)",
style="solid",
)
),
box_shadow="rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px, rgba(0, 0, 0, 0.12) 0px 1px 5px",
cursor="pointer",
width="min(100%, 150px)",
border_radius=12,
background=me.theme_var("background"),
),
):
image_url = screenshots.get(name, "")
me.box(
style=me.Style(
background=f'url("{image_url}") center / cover',
height=112,
width=150,
)
)
me.text(
format_example_name(name),
style=me.Style(
font_weight=500,
font_size=18,
padding=me.Padding.all(12),
border=me.Border(
top=me.BorderSide(
width=1,
style="solid",
color="rgb(220, 220, 220)",
)
),
),
)
def on_load_embed(e: me.LoadEvent):
if me.state(ThemeState).dark_mode:
me.set_theme_mode("dark")
else:
me.set_theme_mode("system")
if not is_desktop():
me.state(State).panel_fullscreen = "preview"
def create_main_fn(example: Example):
@me.page(
on_load=on_load_embed,
title="Mesop Demos",
path="/embed/" + example.name,
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io", "https://huggingface.co"]
),
)
def main():
with me.box(
style=me.Style(
height="100%",
display="flex",
flex_direction="column",
background=me.theme_var("background"),
)
):
header(demo_name=example.name)
body(example.name)
return main
for section in FIRST_SECTIONS + COMPONENTS_SECTIONS:
for example in section.examples:
create_main_fn(example)
def body(current_demo: str):
state = me.state(State)
with me.box(
style=me.Style(
flex_grow=1,
display="flex",
)
):
if is_desktop():
side_menu()
src = "/" + current_demo
with me.box(
style=me.Style(
width="calc(100% - 150px)" if is_desktop() else "100%",
display="grid",
grid_template_columns="1fr 1fr"
if state.panel_fullscreen is None
else "1fr",
)
):
if state.panel_fullscreen != "editor":
demo_ui(src)
if state.panel_fullscreen != "preview":
demo_code(inspect.getsource(get_module(current_demo)))
def demo_ui(src: str):
state = me.state(State)
with me.box(
style=me.Style(flex_grow=1),
):
with me.box(
style=me.Style(
display="flex",
justify_content="space-between",
align_items="center",
border=me.Border(bottom=BORDER_SIDE),
)
):
me.text(
"Preview",
style=me.Style(
font_weight=500,
padding=me.Padding.all(14),
),
)
if is_desktop():
with me.tooltip(
position="above",
message="Minimize"
if state.panel_fullscreen == "preview"
else "Maximize",
):
with me.content_button(type="icon", on_click=toggle_fullscreen):
me.icon(
"close_fullscreen"
if state.panel_fullscreen == "preview"
else "fullscreen"
)
else:
swap_button()
me.embed(
src=src,
style=me.Style(
border=me.Border.all(me.BorderSide(width=0)),
border_radius=2,
height="calc(100vh - 106px)",
width="100%",
),
)
def swap_button():
state = me.state(State)
with me.tooltip(
position="above",
message="Swap for code"
if state.panel_fullscreen == "preview"
else "Swap for preview",
):
with me.content_button(type="icon", on_click=swap_fullscreen):
me.icon("swap_horiz")
def swap_fullscreen(e: me.ClickEvent):
state = me.state(State)
if state.panel_fullscreen == "preview":
state.panel_fullscreen = "editor"
else:
state.panel_fullscreen = "preview"
def toggle_fullscreen(e: me.ClickEvent):
state = me.state(State)
if state.panel_fullscreen == "preview":
state.panel_fullscreen = None
else:
state.panel_fullscreen = "preview"
def demo_code(code_arg: str):
with me.box(
style=me.Style(
flex_grow=1,
overflow_x="hidden",
overflow_y="hidden",
border=me.Border(
left=BORDER_SIDE,
),
background=me.theme_var("surface-container-low"),
)
):
with me.box(
style=me.Style(
display="flex",
justify_content="space-between",
align_items="center",
border=me.Border(bottom=BORDER_SIDE),
background=me.theme_var("background"),
)
):
me.text(
"Code",
style=me.Style(
font_weight=500,
padding=me.Padding.all(14),
),
)
if not is_desktop():
swap_button()
# Use four backticks for code fence to avoid conflicts with backticks being used
# within the displayed code.
me.markdown(
f"""````python
{code_arg}
````
""",
style=me.Style(
border=me.Border(
right=BORDER_SIDE,
),
font_size=13,
height="calc(100vh - 106px)",
overflow_y="auto",
width="100%",
),
)
def header(demo_name: str | None = None):
with me.box(
style=me.Style(
border=me.Border(
bottom=me.BorderSide(
style="solid",
width=1,
color="#dcdcdc",
)
),
overflow_x="clip",
)
):
with me.box(
style=me.Style(
display="flex",
align_items="end",
justify_content="space-between",
margin=me.Margin(left=12, right=12, bottom=12),
font_size=24,
)
):
with me.box(style=me.Style(display="flex")):
with me.box(
style=me.Style(display="flex", cursor="pointer"),
on_click=navigate_home,
):
me.text(
"Mesop", style=me.Style(font_weight=700, margin=me.Margin(right=8))
)
me.text("Demos ")
if demo_name:
me.text(
"— " + format_example_name(demo_name),
style=me.Style(white_space="nowrap", text_overflow="ellipsis"),
)
with me.box(style=me.Style(display="flex", align_items="baseline")):
with me.box(
style=me.Style(
display="flex",
align_items="baseline",
),
):
me.link(
text="google/mesop",
url="https://github.com/google/mesop/",
open_in_new_tab=True,
style=me.Style(
font_size=18,
color=me.theme_var("primary"),
text_decoration="none",
margin=me.Margin(left=8, right=4, bottom=-16, top=-16),
),
)
me.text(
"v" + me.__version__,
style=me.Style(font_size=18, margin=me.Margin(left=16)),
)
with me.content_button(
type="icon",
style=me.Style(left=8, right=4, top=4),
on_click=toggle_theme,
):
me.icon(
"light_mode" if me.theme_brightness() == "dark" else "dark_mode"
)
@me.stateclass
class ThemeState:
dark_mode: bool
def toggle_theme(e: me.ClickEvent):
if me.theme_brightness() == "light":
me.set_theme_mode("dark")
me.state(ThemeState).dark_mode = True
else:
me.set_theme_mode("light")
me.state(ThemeState).dark_mode = False
def navigate_home(e: me.ClickEvent):
me.navigate("/")
def side_menu():
with me.box(
style=me.Style(
padding=me.Padding.all(12),
width=150,
flex_grow=0,
line_height="1.5",
border=me.Border(right=BORDER_SIDE),
overflow_x="hidden",
height="calc(100vh - 60px)",
overflow_y="auto",
)
):
for section in FIRST_SECTIONS:
nav_section(section)
with me.box(
style=me.Style(
margin=me.Margin.symmetric(
horizontal=-16,
vertical=16,
),
)
):
me.divider()
me.text(
"Components",
style=me.Style(
letter_spacing="0.5px",
margin=me.Margin(bottom=6),
),
)
for section in COMPONENTS_SECTIONS:
nav_section(section)
def nav_section(section: Section):
with me.box(style=me.Style(margin=me.Margin(bottom=12))):
me.text(section.name, style=me.Style(font_weight=700))
for example in section.examples:
example_name = format_example_name(example.name)
path = f"/embed/{example.name}"
with me.box(
style=me.Style(color=me.theme_var("primary"), cursor="pointer"),
on_click=set_demo,
key=path,
):
me.text(example_name)
def set_demo(e: me.ClickEvent):
me.navigate(e.key)
def format_example_name(name: str):
return (
(" ".join(name.split("_")))
.capitalize()
.replace("Llm", "LLM")
.replace(" demo", "")
)
def get_module(module_name: str):
if module_name in globals():
return globals()[module_name]
raise me.MesopDeveloperException(f"Module {module_name} not supported")
def is_desktop():
return me.viewport_size().width > 760